Remove legacy MetaDataStateFormat (#31603)
Removes the legacy (pre-1.5) legacy MetaDataStateFormat.
This commit is contained in:
parent
616703b880
commit
01623f66de
|
@ -29,19 +29,17 @@ import org.apache.lucene.store.IOContext;
|
||||||
import org.apache.lucene.store.IndexInput;
|
import org.apache.lucene.store.IndexInput;
|
||||||
import org.apache.lucene.store.OutputStreamIndexOutput;
|
import org.apache.lucene.store.OutputStreamIndexOutput;
|
||||||
import org.apache.lucene.store.SimpleFSDirectory;
|
import org.apache.lucene.store.SimpleFSDirectory;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
|
||||||
import org.elasticsearch.core.internal.io.IOUtils;
|
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
import org.elasticsearch.common.lucene.store.IndexOutputOutputStream;
|
import org.elasticsearch.common.lucene.store.IndexOutputOutputStream;
|
||||||
import org.elasticsearch.common.lucene.store.InputStreamIndexInput;
|
import org.elasticsearch.common.lucene.store.InputStreamIndexInput;
|
||||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
|
import org.elasticsearch.core.internal.io.IOUtils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -54,7 +52,6 @@ import java.nio.file.StandardCopyOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -70,9 +67,8 @@ public abstract class MetaDataStateFormat<T> {
|
||||||
public static final String STATE_FILE_EXTENSION = ".st";
|
public static final String STATE_FILE_EXTENSION = ".st";
|
||||||
|
|
||||||
private static final String STATE_FILE_CODEC = "state";
|
private static final String STATE_FILE_CODEC = "state";
|
||||||
private static final int MIN_COMPATIBLE_STATE_FILE_VERSION = 0;
|
private static final int MIN_COMPATIBLE_STATE_FILE_VERSION = 1;
|
||||||
private static final int STATE_FILE_VERSION = 1;
|
private static final int STATE_FILE_VERSION = 1;
|
||||||
private static final int STATE_FILE_VERSION_ES_2X_AND_BELOW = 0;
|
|
||||||
private static final int BUFFER_SIZE = 4096;
|
private static final int BUFFER_SIZE = 4096;
|
||||||
private final String prefix;
|
private final String prefix;
|
||||||
private final Pattern stateFilePattern;
|
private final Pattern stateFilePattern;
|
||||||
|
@ -186,16 +182,11 @@ public abstract class MetaDataStateFormat<T> {
|
||||||
try (IndexInput indexInput = dir.openInput(file.getFileName().toString(), IOContext.DEFAULT)) {
|
try (IndexInput indexInput = dir.openInput(file.getFileName().toString(), IOContext.DEFAULT)) {
|
||||||
// We checksum the entire file before we even go and parse it. If it's corrupted we barf right here.
|
// We checksum the entire file before we even go and parse it. If it's corrupted we barf right here.
|
||||||
CodecUtil.checksumEntireFile(indexInput);
|
CodecUtil.checksumEntireFile(indexInput);
|
||||||
final int fileVersion = CodecUtil.checkHeader(indexInput, STATE_FILE_CODEC, MIN_COMPATIBLE_STATE_FILE_VERSION,
|
CodecUtil.checkHeader(indexInput, STATE_FILE_CODEC, MIN_COMPATIBLE_STATE_FILE_VERSION, STATE_FILE_VERSION);
|
||||||
STATE_FILE_VERSION);
|
|
||||||
final XContentType xContentType = XContentType.values()[indexInput.readInt()];
|
final XContentType xContentType = XContentType.values()[indexInput.readInt()];
|
||||||
if (xContentType != FORMAT) {
|
if (xContentType != FORMAT) {
|
||||||
throw new IllegalStateException("expected state in " + file + " to be " + FORMAT + " format but was " + xContentType);
|
throw new IllegalStateException("expected state in " + file + " to be " + FORMAT + " format but was " + xContentType);
|
||||||
}
|
}
|
||||||
if (fileVersion == STATE_FILE_VERSION_ES_2X_AND_BELOW) {
|
|
||||||
// format version 0, wrote a version that always came from the content state file and was never used
|
|
||||||
indexInput.readLong(); // version currently unused
|
|
||||||
}
|
|
||||||
long filePointer = indexInput.getFilePointer();
|
long filePointer = indexInput.getFilePointer();
|
||||||
long contentSize = indexInput.length() - CodecUtil.footerLength() - filePointer;
|
long contentSize = indexInput.length() - CodecUtil.footerLength() - filePointer;
|
||||||
try (IndexInput slice = indexInput.slice("state_xcontent", filePointer, contentSize)) {
|
try (IndexInput slice = indexInput.slice("state_xcontent", filePointer, contentSize)) {
|
||||||
|
@ -263,10 +254,9 @@ public abstract class MetaDataStateFormat<T> {
|
||||||
* @param dataLocations the data-locations to try.
|
* @param dataLocations the data-locations to try.
|
||||||
* @return the latest state or <code>null</code> if no state was found.
|
* @return the latest state or <code>null</code> if no state was found.
|
||||||
*/
|
*/
|
||||||
public T loadLatestState(Logger logger, NamedXContentRegistry namedXContentRegistry, Path... dataLocations) throws IOException {
|
public T loadLatestState(Logger logger, NamedXContentRegistry namedXContentRegistry, Path... dataLocations) throws IOException {
|
||||||
List<PathAndStateId> files = new ArrayList<>();
|
List<PathAndStateId> files = new ArrayList<>();
|
||||||
long maxStateId = -1;
|
long maxStateId = -1;
|
||||||
boolean maxStateIdIsLegacy = true;
|
|
||||||
if (dataLocations != null) { // select all eligible files first
|
if (dataLocations != null) { // select all eligible files first
|
||||||
for (Path dataLocation : dataLocations) {
|
for (Path dataLocation : dataLocations) {
|
||||||
final Path stateDir = dataLocation.resolve(STATE_DIR_NAME);
|
final Path stateDir = dataLocation.resolve(STATE_DIR_NAME);
|
||||||
|
@ -280,9 +270,7 @@ public abstract class MetaDataStateFormat<T> {
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
final long stateId = Long.parseLong(matcher.group(1));
|
final long stateId = Long.parseLong(matcher.group(1));
|
||||||
maxStateId = Math.max(maxStateId, stateId);
|
maxStateId = Math.max(maxStateId, stateId);
|
||||||
final boolean legacy = MetaDataStateFormat.STATE_FILE_EXTENSION.equals(matcher.group(2)) == false;
|
PathAndStateId pav = new PathAndStateId(stateFile, stateId);
|
||||||
maxStateIdIsLegacy &= legacy; // on purpose, see NOTE below
|
|
||||||
PathAndStateId pav = new PathAndStateId(stateFile, stateId, legacy);
|
|
||||||
logger.trace("found state file: {}", pav);
|
logger.trace("found state file: {}", pav);
|
||||||
files.add(pav);
|
files.add(pav);
|
||||||
}
|
}
|
||||||
|
@ -292,39 +280,19 @@ public abstract class MetaDataStateFormat<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final List<Throwable> exceptions = new ArrayList<>();
|
|
||||||
T state = null;
|
|
||||||
// NOTE: we might have multiple version of the latest state if there are multiple data dirs.. for this case
|
// NOTE: we might have multiple version of the latest state if there are multiple data dirs.. for this case
|
||||||
// we iterate only over the ones with the max version. If we have at least one state file that uses the
|
// we iterate only over the ones with the max version.
|
||||||
// new format (ie. legacy == false) then we know that the latest version state ought to use this new format.
|
long finalMaxStateId = maxStateId;
|
||||||
// In case the state file with the latest version does not use the new format while older state files do,
|
|
||||||
// the list below will be empty and loading the state will fail
|
|
||||||
Collection<PathAndStateId> pathAndStateIds = files
|
Collection<PathAndStateId> pathAndStateIds = files
|
||||||
.stream()
|
.stream()
|
||||||
.filter(new StateIdAndLegacyPredicate(maxStateId, maxStateIdIsLegacy))
|
.filter(pathAndStateId -> pathAndStateId.id == finalMaxStateId)
|
||||||
.collect(Collectors.toCollection(ArrayList::new));
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
|
||||||
|
final List<Throwable> exceptions = new ArrayList<>();
|
||||||
for (PathAndStateId pathAndStateId : pathAndStateIds) {
|
for (PathAndStateId pathAndStateId : pathAndStateIds) {
|
||||||
try {
|
try {
|
||||||
final Path stateFile = pathAndStateId.file;
|
T state = read(namedXContentRegistry, pathAndStateId.file);
|
||||||
final long id = pathAndStateId.id;
|
logger.trace("state id [{}] read from [{}]", pathAndStateId.id, pathAndStateId.file.getFileName());
|
||||||
if (pathAndStateId.legacy) { // read the legacy format -- plain XContent
|
|
||||||
final byte[] data = Files.readAllBytes(stateFile);
|
|
||||||
if (data.length == 0) {
|
|
||||||
logger.debug("{}: no data for [{}], ignoring...", prefix, stateFile.toAbsolutePath());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try (XContentParser parser = XContentHelper
|
|
||||||
.createParser(namedXContentRegistry, LoggingDeprecationHandler.INSTANCE, new BytesArray(data))) {
|
|
||||||
state = fromXContent(parser);
|
|
||||||
}
|
|
||||||
if (state == null) {
|
|
||||||
logger.debug("{}: no data for [{}], ignoring...", prefix, stateFile.toAbsolutePath());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state = read(namedXContentRegistry, stateFile);
|
|
||||||
logger.trace("state id [{}] read from [{}]", id, stateFile.getFileName());
|
|
||||||
}
|
|
||||||
return state;
|
return state;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
exceptions.add(new IOException("failed to read " + pathAndStateId.toString(), e));
|
exceptions.add(new IOException("failed to read " + pathAndStateId.toString(), e));
|
||||||
|
@ -338,46 +306,24 @@ public abstract class MetaDataStateFormat<T> {
|
||||||
// We have some state files but none of them gave us a usable state
|
// We have some state files but none of them gave us a usable state
|
||||||
throw new IllegalStateException("Could not find a state file to recover from among " + files);
|
throw new IllegalStateException("Could not find a state file to recover from among " + files);
|
||||||
}
|
}
|
||||||
return state;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters out all {@link org.elasticsearch.gateway.MetaDataStateFormat.PathAndStateId} instances with a different id than
|
* Internal struct-like class that holds the parsed state id and the file
|
||||||
* the given one.
|
|
||||||
*/
|
|
||||||
private static final class StateIdAndLegacyPredicate implements Predicate<PathAndStateId> {
|
|
||||||
private final long id;
|
|
||||||
private final boolean legacy;
|
|
||||||
|
|
||||||
StateIdAndLegacyPredicate(long id, boolean legacy) {
|
|
||||||
this.id = id;
|
|
||||||
this.legacy = legacy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean test(PathAndStateId input) {
|
|
||||||
return input.id == id && input.legacy == legacy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal struct-like class that holds the parsed state id, the file
|
|
||||||
* and a flag if the file is a legacy state ie. pre 1.5
|
|
||||||
*/
|
*/
|
||||||
private static class PathAndStateId {
|
private static class PathAndStateId {
|
||||||
final Path file;
|
final Path file;
|
||||||
final long id;
|
final long id;
|
||||||
final boolean legacy;
|
|
||||||
|
|
||||||
private PathAndStateId(Path file, long id, boolean legacy) {
|
private PathAndStateId(Path file, long id) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.legacy = legacy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[id:" + id + ", legacy:" + legacy + ", file:" + file.toAbsolutePath() + "]";
|
return "[id:" + id + ", file:" + file.toAbsolutePath() + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,16 +81,12 @@ public class RecoveryWithUnsupportedIndicesIT extends ESIntegTestCase {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testUpgradeStartClusterOn_0_20_6() throws Exception {
|
public void testUpgradeStartClusterOn_2_4_5() throws Exception {
|
||||||
String indexName = "unsupported-0.20.6";
|
String indexName = "unsupported-2.4.5";
|
||||||
|
|
||||||
logger.info("Checking static index {}", indexName);
|
logger.info("Checking static index {}", indexName);
|
||||||
Settings nodeSettings = prepareBackwardsDataDir(getBwcIndicesPath().resolve(indexName + ".zip"));
|
Settings nodeSettings = prepareBackwardsDataDir(getBwcIndicesPath().resolve(indexName + ".zip"));
|
||||||
try {
|
assertThat(expectThrows(Exception.class, () -> internalCluster().startNode(nodeSettings))
|
||||||
internalCluster().startNode(nodeSettings);
|
.getCause().getCause().getMessage(), containsString("Format version is not supported"));
|
||||||
fail();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
assertThat(ex.getCause().getCause().getMessage(), containsString(" was created before v2.0.0.beta1 and wasn't upgraded"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.ToXContentFragment;
|
import org.elasticsearch.common.xcontent.ToXContentFragment;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -92,7 +91,7 @@ public class MetaDataStateFormatTests extends ESTestCase {
|
||||||
Files.copy(resource, dst);
|
Files.copy(resource, dst);
|
||||||
MetaData read = format.read(xContentRegistry(), dst);
|
MetaData read = format.read(xContentRegistry(), dst);
|
||||||
assertThat(read, notNullValue());
|
assertThat(read, notNullValue());
|
||||||
assertThat(read.clusterUUID(), equalTo("3O1tDF1IRB6fSJ-GrTMUtg"));
|
assertThat(read.clusterUUID(), equalTo("y9XcwLJGTROoOEfixlRwfQ"));
|
||||||
// indices are empty since they are serialized separately
|
// indices are empty since they are serialized separately
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +236,6 @@ public class MetaDataStateFormatTests extends ESTestCase {
|
||||||
public void testLoadState() throws IOException {
|
public void testLoadState() throws IOException {
|
||||||
final Path[] dirs = new Path[randomIntBetween(1, 5)];
|
final Path[] dirs = new Path[randomIntBetween(1, 5)];
|
||||||
int numStates = randomIntBetween(1, 5);
|
int numStates = randomIntBetween(1, 5);
|
||||||
int numLegacy = randomIntBetween(0, numStates);
|
|
||||||
List<MetaData> meta = new ArrayList<>();
|
List<MetaData> meta = new ArrayList<>();
|
||||||
for (int i = 0; i < numStates; i++) {
|
for (int i = 0; i < numStates; i++) {
|
||||||
meta.add(randomMeta());
|
meta.add(randomMeta());
|
||||||
|
@ -247,20 +245,7 @@ public class MetaDataStateFormatTests extends ESTestCase {
|
||||||
for (int i = 0; i < dirs.length; i++) {
|
for (int i = 0; i < dirs.length; i++) {
|
||||||
dirs[i] = createTempDir();
|
dirs[i] = createTempDir();
|
||||||
Files.createDirectories(dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME));
|
Files.createDirectories(dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME));
|
||||||
for (int j = 0; j < numLegacy; j++) {
|
for (int j = 0; j < numStates; j++) {
|
||||||
if (randomBoolean() && (j < numStates - 1 || dirs.length > 0 && i != 0)) {
|
|
||||||
Path file = dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME).resolve("global-"+j);
|
|
||||||
Files.createFile(file); // randomly create 0-byte files -- there is extra logic to skip them
|
|
||||||
} else {
|
|
||||||
try (XContentBuilder xcontentBuilder = XContentFactory.contentBuilder(MetaDataStateFormat.FORMAT,
|
|
||||||
Files.newOutputStream(dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME).resolve("global-" + j)))) {
|
|
||||||
xcontentBuilder.startObject();
|
|
||||||
MetaData.Builder.toXContent(meta.get(j), xcontentBuilder, ToXContent.EMPTY_PARAMS);
|
|
||||||
xcontentBuilder.endObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int j = numLegacy; j < numStates; j++) {
|
|
||||||
format.write(meta.get(j), dirs[i]);
|
format.write(meta.get(j), dirs[i]);
|
||||||
if (randomBoolean() && (j < numStates - 1 || dirs.length > 0 && i != 0)) { // corrupt a file that we do not necessarily need here....
|
if (randomBoolean() && (j < numStates - 1 || dirs.length > 0 && i != 0)) { // corrupt a file that we do not necessarily need here....
|
||||||
Path file = dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME).resolve("global-" + j + ".st");
|
Path file = dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME).resolve("global-" + j + ".st");
|
||||||
|
@ -290,20 +275,18 @@ public class MetaDataStateFormatTests extends ESTestCase {
|
||||||
assertThat(loadedMetaData.indexGraveyard(), equalTo(latestMetaData.indexGraveyard()));
|
assertThat(loadedMetaData.indexGraveyard(), equalTo(latestMetaData.indexGraveyard()));
|
||||||
|
|
||||||
// now corrupt all the latest ones and make sure we fail to load the state
|
// now corrupt all the latest ones and make sure we fail to load the state
|
||||||
if (numStates > numLegacy) {
|
for (int i = 0; i < dirs.length; i++) {
|
||||||
for (int i = 0; i < dirs.length; i++) {
|
Path file = dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME).resolve("global-" + (numStates-1) + ".st");
|
||||||
Path file = dirs[i].resolve(MetaDataStateFormat.STATE_DIR_NAME).resolve("global-" + (numStates-1) + ".st");
|
if (corruptedFiles.contains(file)) {
|
||||||
if (corruptedFiles.contains(file)) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
MetaDataStateFormatTests.corruptFile(file, logger);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
format.loadLatestState(logger, xContentRegistry(), dirList.toArray(new Path[0]));
|
|
||||||
fail("latest version can not be read");
|
|
||||||
} catch (ElasticsearchException ex) {
|
|
||||||
assertThat(ExceptionsHelper.unwrap(ex, CorruptStateException.class), notNullValue());
|
|
||||||
}
|
}
|
||||||
|
MetaDataStateFormatTests.corruptFile(file, logger);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
format.loadLatestState(logger, xContentRegistry(), dirList.toArray(new Path[0]));
|
||||||
|
fail("latest version can not be read");
|
||||||
|
} catch (ElasticsearchException ex) {
|
||||||
|
assertThat(ExceptionsHelper.unwrap(ex, CorruptStateException.class), notNullValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue