blob: 757d7e856d63887575851a6fd61066efb3b2ee68 [file] [log] [blame]
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.ozone.container.common.impl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ContainerType;
import org.apache.hadoop.hdds.scm.container.common.helpers
.StorageContainerException;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.container.keyvalue.KeyValueContainerData;
import com.google.common.base.Preconditions;
import static org.apache.hadoop.ozone.container.keyvalue
.KeyValueContainerData.KEYVALUE_YAML_TAG;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.AbstractConstruct;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
/**
* Class for creating and reading .container files.
*/
public final class ContainerDataYaml {
private static final Logger LOG =
LoggerFactory.getLogger(ContainerDataYaml.class);
private ContainerDataYaml() {
}
/**
* Creates a .container file in yaml format.
*
* @param containerFile
* @param containerData
* @throws IOException
*/
public static void createContainerFile(ContainerType containerType,
ContainerData containerData, File containerFile) throws IOException {
Writer writer = null;
FileOutputStream out = null;
try {
// Create Yaml for given container type
Yaml yaml = getYamlForContainerType(containerType);
// Compute Checksum and update ContainerData
containerData.computeAndSetChecksum(yaml);
// Write the ContainerData with checksum to Yaml file.
out = new FileOutputStream(
containerFile);
writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
yaml.dump(containerData, writer);
} finally {
try {
if (writer != null) {
writer.flush();
// make sure the container metadata is synced to disk.
out.getFD().sync();
writer.close();
}
} catch (IOException ex) {
LOG.warn("Error occurred during closing the writer. ContainerID: " +
containerData.getContainerID());
}
IOUtils.closeQuietly(out);
}
}
/**
* Read the yaml file, and return containerData.
*
* @throws IOException
*/
public static ContainerData readContainerFile(File containerFile)
throws IOException {
Preconditions.checkNotNull(containerFile, "containerFile cannot be null");
try (FileInputStream inputFileStream = new FileInputStream(containerFile)) {
return readContainer(inputFileStream);
}
}
/**
* Read the yaml file content, and return containerData.
*
* @throws IOException
*/
public static ContainerData readContainer(byte[] containerFileContent)
throws IOException {
return readContainer(
new ByteArrayInputStream(containerFileContent));
}
/**
* Read the yaml content, and return containerData.
*
* @throws IOException
*/
public static ContainerData readContainer(InputStream input)
throws IOException {
ContainerData containerData;
PropertyUtils propertyUtils = new PropertyUtils();
propertyUtils.setBeanAccess(BeanAccess.FIELD);
propertyUtils.setAllowReadOnlyProperties(true);
Representer representer = new ContainerDataRepresenter();
representer.setPropertyUtils(propertyUtils);
Constructor containerDataConstructor = new ContainerDataConstructor();
Yaml yaml = new Yaml(containerDataConstructor, representer);
yaml.setBeanAccess(BeanAccess.FIELD);
containerData = (ContainerData)
yaml.load(input);
return containerData;
}
/**
* Given a ContainerType this method returns a Yaml representation of
* the container properties.
*
* @param containerType type of container
* @return Yamal representation of container properties
*
* @throws StorageContainerException if the type is unrecognized
*/
public static Yaml getYamlForContainerType(ContainerType containerType)
throws StorageContainerException {
PropertyUtils propertyUtils = new PropertyUtils();
propertyUtils.setBeanAccess(BeanAccess.FIELD);
propertyUtils.setAllowReadOnlyProperties(true);
switch (containerType) {
case KeyValueContainer:
Representer representer = new ContainerDataRepresenter();
representer.setPropertyUtils(propertyUtils);
representer.addClassTag(
KeyValueContainerData.class,
KeyValueContainerData.KEYVALUE_YAML_TAG);
Constructor keyValueDataConstructor = new ContainerDataConstructor();
return new Yaml(keyValueDataConstructor, representer);
default:
throw new StorageContainerException("Unrecognized container Type " +
"format " + containerType, ContainerProtos.Result
.UNKNOWN_CONTAINER_TYPE);
}
}
/**
* Representer class to define which fields need to be stored in yaml file.
*/
private static class ContainerDataRepresenter extends Representer {
@Override
protected Set<Property> getProperties(Class<? extends Object> type) {
Set<Property> set = super.getProperties(type);
Set<Property> filtered = new TreeSet<Property>();
// When a new Container type is added, we need to add what fields need
// to be filtered here
if (type.equals(KeyValueContainerData.class)) {
List<String> yamlFields = KeyValueContainerData.getYamlFields();
// filter properties
for (Property prop : set) {
String name = prop.getName();
if (yamlFields.contains(name)) {
filtered.add(prop);
}
}
}
return filtered;
}
/**
* Omit properties with null value.
*/
@Override
protected NodeTuple representJavaBeanProperty(
Object bean, Property property, Object value, Tag tag) {
return value == null
? null
: super.representJavaBeanProperty(bean, property, value, tag);
}
}
/**
* Constructor class for KeyValueData, which will be used by Yaml.
*/
private static class ContainerDataConstructor extends Constructor {
ContainerDataConstructor() {
//Adding our own specific constructors for tags.
// When a new Container type is added, we need to add yamlConstructor
// for that
this.yamlConstructors.put(
KEYVALUE_YAML_TAG, new ConstructKeyValueContainerData());
this.yamlConstructors.put(Tag.INT, new ConstructLong());
}
private class ConstructKeyValueContainerData extends AbstractConstruct {
public Object construct(Node node) {
MappingNode mnode = (MappingNode) node;
Map<Object, Object> nodes = constructMapping(mnode);
//Needed this, as TAG.INT type is by default converted to Long.
long layOutVersion = (long) nodes.get(OzoneConsts.LAYOUTVERSION);
ChunkLayOutVersion layoutVersion =
ChunkLayOutVersion.getChunkLayOutVersion((int) layOutVersion);
long size = (long) nodes.get(OzoneConsts.MAX_SIZE);
String originPipelineId = (String) nodes.get(
OzoneConsts.ORIGIN_PIPELINE_ID);
String originNodeId = (String) nodes.get(OzoneConsts.ORIGIN_NODE_ID);
//When a new field is added, it needs to be added here.
KeyValueContainerData kvData = new KeyValueContainerData(
(long) nodes.get(OzoneConsts.CONTAINER_ID), layoutVersion, size,
originPipelineId, originNodeId);
kvData.setContainerDBType((String)nodes.get(
OzoneConsts.CONTAINER_DB_TYPE));
kvData.setMetadataPath((String) nodes.get(
OzoneConsts.METADATA_PATH));
kvData.setChunksPath((String) nodes.get(OzoneConsts.CHUNKS_PATH));
Map<String, String> meta = (Map) nodes.get(OzoneConsts.METADATA);
kvData.setMetadata(meta);
kvData.setChecksum((String) nodes.get(OzoneConsts.CHECKSUM));
Long timestamp = (Long) nodes.get(OzoneConsts.DATA_SCAN_TIMESTAMP);
kvData.setDataScanTimestamp(timestamp);
String state = (String) nodes.get(OzoneConsts.STATE);
kvData
.setState(ContainerProtos.ContainerDataProto.State.valueOf(state));
String schemaVersion = (String) nodes.get(OzoneConsts.SCHEMA_VERSION);
kvData.setSchemaVersion(schemaVersion);
return kvData;
}
}
//Below code is taken from snake yaml, as snakeyaml tries to fit the
// number if it fits in integer, otherwise returns long. So, slightly
// modified the code to return long in all cases.
private class ConstructLong extends AbstractConstruct {
public Object construct(Node node) {
String value = constructScalar((ScalarNode) node).toString()
.replaceAll("_", "");
int sign = +1;
char first = value.charAt(0);
if (first == '-') {
sign = -1;
value = value.substring(1);
} else if (first == '+') {
value = value.substring(1);
}
int base = 10;
if ("0".equals(value)) {
return Long.valueOf(0);
} else if (value.startsWith("0b")) {
value = value.substring(2);
base = 2;
} else if (value.startsWith("0x")) {
value = value.substring(2);
base = 16;
} else if (value.startsWith("0")) {
value = value.substring(1);
base = 8;
} else if (value.indexOf(':') != -1) {
String[] digits = value.split(":");
int bes = 1;
int val = 0;
for (int i = 0, j = digits.length; i < j; i++) {
val += (Long.parseLong(digits[(j - i) - 1]) * bes);
bes *= 60;
}
return createNumber(sign, String.valueOf(val), 10);
} else {
return createNumber(sign, value, 10);
}
return createNumber(sign, value, base);
}
}
private Number createNumber(int sign, String number, int radix) {
Number result;
if (sign < 0) {
number = "-" + number;
}
result = Long.valueOf(number, radix);
return result;
}
}
}