-
Notifications
You must be signed in to change notification settings - Fork 8.8k
/
NodeSchemaLoader.java
480 lines (455 loc) · 17.7 KB
/
NodeSchemaLoader.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdds.scm.net;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.hdds.scm.net.NodeSchema.LayerType;
import org.yaml.snakeyaml.Yaml;
/**
* A Network topology layer schema loading tool that loads user defined network
* layer schema data from a XML configuration file.
*/
public final class NodeSchemaLoader {
private static final Logger LOG
= LoggerFactory.getLogger(NodeSchemaLoader.class);
private static final String CONFIGURATION_TAG = "configuration";
private static final String LAYOUT_VERSION_TAG = "layoutversion";
private static final String TOPOLOGY_TAG = "topology";
private static final String TOPOLOGY_PATH = "path";
private static final String TOPOLOGY_ENFORCE_PREFIX = "enforceprefix";
private static final String LAYERS_TAG = "layers";
private static final String LAYER_TAG = "layer";
private static final String LAYER_ID = "id";
private static final String LAYER_TYPE = "type";
private static final String LAYER_COST = "cost";
private static final String LAYER_PREFIX = "prefix";
private static final String LAYER_DEFAULT_NAME = "default";
private static final int LAYOUT_VERSION = 1;
private volatile static NodeSchemaLoader instance = null;
private NodeSchemaLoader() {}
public static NodeSchemaLoader getInstance() {
if (instance == null) {
instance = new NodeSchemaLoader();
}
return instance;
}
/**
* Class to house keep the result of parsing a network topology schema file.
*/
public static class NodeSchemaLoadResult {
private List<NodeSchema> schemaList;
private boolean enforcePrefix;
NodeSchemaLoadResult(List<NodeSchema> schemaList, boolean enforcePrefix) {
this.schemaList = schemaList;
this.enforcePrefix = enforcePrefix;
}
public boolean isEnforePrefix() {
return enforcePrefix;
}
public List<NodeSchema> getSchemaList() {
return schemaList;
}
}
/**
* Load user defined network layer schemas from a XML/YAML configuration file.
* @param schemaFilePath path of schema file
* @return all valid node schemas defined in schema file
*/
public NodeSchemaLoadResult loadSchemaFromFile(String schemaFilePath)
throws IllegalArgumentException, FileNotFoundException {
try {
File schemaFile = new File(schemaFilePath);
if (!schemaFile.exists()) {
// try to load with classloader
ClassLoader classloader =
Thread.currentThread().getContextClassLoader();
if (classloader == null) {
classloader = NodeSchemaLoader.class.getClassLoader();
}
if (classloader != null) {
URL url = classloader.getResource(schemaFilePath);
if (url != null) {
schemaFile = new File(url.toURI());
}
}
}
if (!schemaFile.exists()) {
String msg = "Network topology layer schema file " +
schemaFilePath + "[" + schemaFile.getAbsolutePath() +
"] is not found.";
LOG.warn(msg);
throw new FileNotFoundException(msg);
}
LOG.info("Load network topology schema file " +
schemaFile.getCanonicalPath());
if (FilenameUtils.getExtension(schemaFilePath).toLowerCase()
.compareTo("yaml") == 0) {
return loadSchemaFromYaml(schemaFile);
} else {
return loadSchema(schemaFile);
}
} catch (FileNotFoundException e) {
throw e;
} catch (ParserConfigurationException | IOException | SAXException |
URISyntaxException e) {
throw new IllegalArgumentException("Failed to load network topology node"
+ " schema file: " + schemaFilePath + " , error:" + e.getMessage());
}
}
/**
* Load network topology layer schemas from a XML configuration file.
* @param schemaFile schema file
* @return all valid node schemas defined in schema file
* @throws ParserConfigurationException ParserConfigurationException happen
* @throws IOException no such schema file
* @throws SAXException xml file has some invalid elements
* @throws IllegalArgumentException xml file content is logically invalid
*/
private NodeSchemaLoadResult loadSchema(File schemaFile) throws
ParserConfigurationException, SAXException, IOException {
LOG.info("Loading network topology layer schema file " + schemaFile);
// Read and parse the schema file.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setIgnoringComments(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(schemaFile);
Element root = doc.getDocumentElement();
if (!CONFIGURATION_TAG.equals(root.getTagName())) {
throw new IllegalArgumentException("Bad network topology layer schema " +
"configuration file: top-level element not <" + CONFIGURATION_TAG +
">");
}
NodeSchemaLoadResult schemaList;
if (root.getElementsByTagName(LAYOUT_VERSION_TAG).getLength() == 1) {
if (loadLayoutVersion(root) == LAYOUT_VERSION) {
if (root.getElementsByTagName(LAYERS_TAG).getLength() == 1) {
Map<String, NodeSchema> schemas = loadLayersSection(root);
if (root.getElementsByTagName(TOPOLOGY_TAG).getLength() == 1) {
schemaList = loadTopologySection(root, schemas);
} else {
throw new IllegalArgumentException("Bad network topology layer " +
"schema configuration file: no or multiple <" + TOPOLOGY_TAG +
"> element");
}
} else {
throw new IllegalArgumentException("Bad network topology layer schema"
+ " configuration file: no or multiple <" + LAYERS_TAG +
">element");
}
} else {
throw new IllegalArgumentException("The parse failed because of bad "
+ LAYOUT_VERSION_TAG + " value, expected:" + LAYOUT_VERSION);
}
} else {
throw new IllegalArgumentException("Bad network topology layer schema " +
"configuration file: no or multiple <" + LAYOUT_VERSION_TAG +
"> elements");
}
return schemaList;
}
/**
* Load network topology layer schemas from a YAML configuration file.
* @param schemaFile schema file
* @return all valid node schemas defined in schema file
* @throws ParserConfigurationException ParserConfigurationException happen
* @throws IOException no such schema file
* @throws SAXException xml file has some invalid elements
* @throws IllegalArgumentException xml file content is logically invalid
*/
private NodeSchemaLoadResult loadSchemaFromYaml(File schemaFile) {
LOG.info("Loading network topology layer schema file {}", schemaFile);
NodeSchemaLoadResult finalSchema;
try {
Yaml yaml = new Yaml();
NodeSchema nodeTree;
try (FileInputStream fileInputStream = new FileInputStream(schemaFile)) {
nodeTree = yaml.loadAs(fileInputStream, NodeSchema.class);
}
List<NodeSchema> schemaList = new ArrayList<>();
if (nodeTree.getType() != LayerType.ROOT) {
throw new IllegalArgumentException("First layer is not a ROOT node."
+ " schema file: " + schemaFile.getAbsolutePath());
}
schemaList.add(nodeTree);
if (nodeTree.getSublayer() != null) {
nodeTree = nodeTree.getSublayer().get(0);
}
while (nodeTree != null) {
if (nodeTree.getType() == LayerType.LEAF_NODE
&& nodeTree.getSublayer() != null) {
throw new IllegalArgumentException("Leaf node in the middle of path."
+ " schema file: " + schemaFile.getAbsolutePath());
}
if (nodeTree.getType() == LayerType.ROOT) {
throw new IllegalArgumentException("Multiple root nodes are defined."
+ " schema file: " + schemaFile.getAbsolutePath());
}
schemaList.add(nodeTree);
if (nodeTree.getSublayer() != null) {
nodeTree = nodeTree.getSublayer().get(0);
} else {
break;
}
}
finalSchema = new NodeSchemaLoadResult(schemaList, true);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException("Fail to load network topology node"
+ " schema file: " + schemaFile.getAbsolutePath() + " , error:"
+ e.getMessage());
}
return finalSchema;
}
/**
* Load layoutVersion from root element in the XML configuration file.
* @param root root element
* @return layout version
*/
private int loadLayoutVersion(Element root) {
int layoutVersion;
Text text = (Text) root.getElementsByTagName(LAYOUT_VERSION_TAG)
.item(0).getFirstChild();
if (text != null) {
String value = text.getData().trim();
try {
layoutVersion = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Bad " + LAYOUT_VERSION_TAG +
" value " + value + " is found. It should be an integer.");
}
} else {
throw new IllegalArgumentException("Value of <" + LAYOUT_VERSION_TAG +
"> is null");
}
return layoutVersion;
}
/**
* Load layers from root element in the XML configuration file.
* @param root root element
* @return A map of node schemas with layer ID and layer schema
*/
private Map<String, NodeSchema> loadLayersSection(Element root) {
NodeList elements = root.getElementsByTagName(LAYER_TAG);
Map<String, NodeSchema> schemas = new HashMap<String, NodeSchema>();
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (node instanceof Element) {
Element element = (Element) node;
if (LAYER_TAG.equals(element.getTagName())) {
String layerId = element.getAttribute(LAYER_ID);
NodeSchema schema = parseLayerElement(element);
if (!schemas.containsValue(schema)) {
schemas.put(layerId, schema);
} else {
throw new IllegalArgumentException("Repetitive layer in network " +
"topology node schema configuration file: " + layerId);
}
} else {
throw new IllegalArgumentException("Bad element in network topology "
+ "node schema configuration file: " + element.getTagName());
}
}
}
// Integrity check, only one ROOT and one LEAF is allowed
boolean foundRoot = false;
boolean foundLeaf = false;
for(NodeSchema schema: schemas.values()) {
if (schema.getType() == LayerType.ROOT) {
if (foundRoot) {
throw new IllegalArgumentException("Multiple ROOT layers are found" +
" in network topology schema configuration file");
} else {
foundRoot = true;
}
}
if (schema.getType() == LayerType.LEAF_NODE) {
if (foundLeaf) {
throw new IllegalArgumentException("Multiple LEAF layers are found" +
" in network topology schema configuration file");
} else {
foundLeaf = true;
}
}
}
if (!foundRoot) {
throw new IllegalArgumentException("No ROOT layer is found" +
" in network topology schema configuration file");
}
if (!foundLeaf) {
throw new IllegalArgumentException("No LEAF layer is found" +
" in network topology schema configuration file");
}
return schemas;
}
/**
* Load network topology from root element in the XML configuration file and
* sort node schemas according to the topology path.
* @param root root element
* @param schemas schema map
* @return all valid node schemas defined in schema file
*/
private NodeSchemaLoadResult loadTopologySection(Element root,
Map<String, NodeSchema> schemas) {
NodeList elements = root.getElementsByTagName(TOPOLOGY_TAG)
.item(0).getChildNodes();
List<NodeSchema> schemaList = new ArrayList<NodeSchema>();
boolean enforecePrefix = false;
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (node instanceof Element) {
Element element = (Element) node;
String tagName = element.getTagName();
// Get the nonnull text value.
Text text = (Text) element.getFirstChild();
String value;
if (text != null) {
value = text.getData().trim();
if (value.isEmpty()) {
// Element with empty value is ignored
continue;
}
} else {
throw new IllegalArgumentException("Value of <" + tagName
+ "> is null");
}
if (TOPOLOGY_PATH.equals(tagName)) {
if(value.startsWith(NetConstants.PATH_SEPARATOR_STR)) {
value = value.substring(1, value.length());
}
String[] layerIDs = value.split(NetConstants.PATH_SEPARATOR_STR);
if (layerIDs == null || layerIDs.length != schemas.size()) {
throw new IllegalArgumentException("Topology path depth doesn't "
+ "match layer element numbers");
}
for (int j = 0; j < layerIDs.length; j++) {
if (schemas.get(layerIDs[j]) == null) {
throw new IllegalArgumentException("No layer found for id " +
layerIDs[j]);
}
}
if (schemas.get(layerIDs[0]).getType() != LayerType.ROOT) {
throw new IllegalArgumentException("Topology path doesn't start "
+ "with ROOT layer");
}
if (schemas.get(layerIDs[layerIDs.length -1]).getType() !=
LayerType.LEAF_NODE) {
throw new IllegalArgumentException("Topology path doesn't end "
+ "with LEAF layer");
}
for (int j = 0; j < layerIDs.length; j++) {
schemaList.add(schemas.get(layerIDs[j]));
}
} else if (TOPOLOGY_ENFORCE_PREFIX.equalsIgnoreCase(tagName)) {
enforecePrefix = Boolean.parseBoolean(value);
} else {
throw new IllegalArgumentException("Unsupported Element <" +
tagName + ">");
}
}
}
// Integrity check
if (enforecePrefix) {
// Every InnerNode should have prefix defined
for (NodeSchema schema: schemas.values()) {
if (schema.getType() == LayerType.INNER_NODE &&
schema.getPrefix() == null) {
throw new IllegalArgumentException("There is layer without prefix " +
"defined while prefix is enforced.");
}
}
}
return new NodeSchemaLoadResult(schemaList, enforecePrefix);
}
/**
* Load a layer from a layer element in the XML configuration file.
* @param element network topology node layer element
* @return ECSchema
*/
private NodeSchema parseLayerElement(Element element) {
NodeList fields = element.getChildNodes();
LayerType type = null;
int cost = 0;
String prefix = null;
String defaultName = null;
for (int i = 0; i < fields.getLength(); i++) {
Node fieldNode = fields.item(i);
if (fieldNode instanceof Element) {
Element field = (Element) fieldNode;
String tagName = field.getTagName();
// Get the nonnull text value.
Text text = (Text) field.getFirstChild();
String value;
if (text != null) {
value = text.getData().trim();
if (value.isEmpty()) {
// Element with empty value is ignored
continue;
}
} else {
continue;
}
if (LAYER_COST.equalsIgnoreCase(tagName)) {
cost = Integer.parseInt(value);
if (cost < 0) {
throw new IllegalArgumentException(
"Cost should be positive number or 0");
}
} else if (LAYER_TYPE.equalsIgnoreCase(tagName)) {
type = NodeSchema.LayerType.getType(value);
if (type == null) {
throw new IllegalArgumentException(
"Unsupported layer type:" + value);
}
} else if (LAYER_PREFIX.equalsIgnoreCase(tagName)) {
prefix = value;
} else if (LAYER_DEFAULT_NAME.equalsIgnoreCase(tagName)) {
defaultName = value;
} else {
throw new IllegalArgumentException("Unsupported Element <" + tagName
+ ">");
}
}
}
// type is a mandatory property
if (type == null) {
throw new IllegalArgumentException("Missing type Element");
}
return new NodeSchema(type, cost, prefix, defaultName);
}
}