Skip to content

Commit

Permalink
Merge pull request #2 from lostcharlie/feature-tree-datum-store
Browse files Browse the repository at this point in the history
Implement a persistent store for tree protocol
  • Loading branch information
lostcharlie authored Oct 31, 2019
2 parents 389634b + 39ea97a commit ab2d03d
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed 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 com.alibaba.nacos.naming.consistency.weak.tree;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.monitor.MetricsMonitor;
import com.alibaba.nacos.naming.pojo.Record;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

/**
* Datum store for tree protocol.
*
* @author lostcharlie
*/
@Component
public class TreeDataStore {
private String basePath;

public String getBasePath() {
return basePath;
}

@Value("${nacos.naming.tree.dataStore.basePath}")
private void setBasePath(String basePath) {
this.basePath = basePath;
}

public Datum read(String key, Class<? extends Record> valueType) throws IOException {
synchronized (this.getLock(key)) {
FileChannel fileChannel = null;
try {
File file = new File(this.getFileName(key));
fileChannel = new FileInputStream(file).getChannel();
ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
fileChannel.read(buffer);

String json = new String(buffer.array(), StandardCharsets.UTF_8);
if (StringUtils.isBlank(json)) {
return null;
}
JSONObject jsonObject = JSON.parseObject(json);
Datum datum = new Datum();
datum.timestamp.set(jsonObject.getLongValue("timestamp"));
datum.key = jsonObject.getString("key");
datum.value = JSON.parseObject(jsonObject.getString("value"), valueType);
return datum;
} catch (IOException exception) {
MetricsMonitor.getDiskException().increment();
throw exception;
} finally {
if (fileChannel != null) {
fileChannel.close();
}
}
}
}

public void write(Datum datum) throws IOException {
synchronized (this.getLock(datum.key)) {
File file = new File(this.getFileName(datum.key));
if (!file.exists() && !file.getParentFile().mkdirs() && !file.createNewFile()) {
MetricsMonitor.getDiskException().increment();
throw new IllegalStateException("can not make file: " + file.getName());
}

FileChannel fileChannel = null;
try {
ByteBuffer data = ByteBuffer.wrap(JSON.toJSONString(datum).getBytes(StandardCharsets.UTF_8));
fileChannel = new FileOutputStream(file, false).getChannel();
fileChannel.write(data, data.position());
fileChannel.force(true);
} catch (IOException exception) {
MetricsMonitor.getDiskException().increment();
throw exception;
} finally {
if (fileChannel != null) {
fileChannel.close();
}
}
}
}

public void remove(String key) {
synchronized (this.getLock(key)) {
File file = new File(this.getFileName(key));
if (file.exists() && !file.delete()) {
throw new IllegalStateException("failed to delete datum: " + key);
}
}
}

public String getFileName(String key) {
String namespaceId = KeyBuilder.getNamespace(key);
String fileName;
if (StringUtils.isNotBlank(namespaceId)) {
fileName = this.getBasePath() + File.separator + namespaceId + File.separator + this.encodeFileName(key);
} else {
fileName = this.getBasePath() + File.separator + this.encodeFileName(key);
}
return fileName;
}

private String getLock(String key) {
return this.getFileName(key).intern();
}

private String encodeFileName(String fileName) {
return fileName.replace(':', '#');
}

private String decodeFileName(String fileName) {
return fileName.replace("#", ":");
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed 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 com.alibaba.nacos.naming.consistency.weak.tree;

import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.core.Instance;
import com.alibaba.nacos.naming.core.Instances;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;

import java.io.File;
import java.util.UUID;

/**
* @author lostcharlie
*/
public class TreeDataStoreTest {
@Test
public void testReadDatum() throws Exception {
String basePath = System.getProperty("user.dir") + File.separator + "tree-datum-test";
TreeDataStore treeDataStore = new TreeDataStore();
ReflectionTestUtils.setField(treeDataStore, "basePath", basePath);

String namespaceId = UUID.randomUUID().toString();
String serviceName = UUID.randomUUID().toString();
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, false);

Instances value = new Instances();
Instance instance = new Instance("192.168.0.1", 8888);
value.getInstanceList().add(instance);
Datum datum = new Datum();
datum.key = key;
datum.value = value;
datum.timestamp.set(0L);

treeDataStore.write(datum);
Assert.assertTrue(new File(basePath).exists());
Assert.assertTrue(treeDataStore.getFileName(datum.key).startsWith(basePath));
Assert.assertTrue(new File(treeDataStore.getFileName(datum.key)).exists());

Datum actual = treeDataStore.read(datum.key, Instances.class);
Assert.assertNotNull(actual);
Assert.assertEquals(datum.key, actual.key);
Assert.assertEquals(datum.timestamp.get(), actual.timestamp.get());
Assert.assertEquals(1, ((Instances) actual.value).getInstanceList().size());
Assert.assertTrue(((Instances) actual.value).getInstanceList().contains(instance));

TreeDataStoreTest.cleanUp(new File(basePath));
}

@Test
public void testWriteDatum() throws Exception {
String basePath = System.getProperty("user.dir") + File.separator + "tree-datum-test";
TreeDataStore treeDataStore = new TreeDataStore();
ReflectionTestUtils.setField(treeDataStore, "basePath", basePath);

String namespaceId = UUID.randomUUID().toString();
String serviceName = UUID.randomUUID().toString();
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, false);

Instances value = new Instances();
Instance instance = new Instance("192.168.0.1", 8888);
value.getInstanceList().add(instance);
Datum datum = new Datum();
datum.key = key;
datum.value = value;
datum.timestamp.set(0L);

treeDataStore.write(datum);
Assert.assertTrue(new File(basePath).exists());
Assert.assertTrue(treeDataStore.getFileName(datum.key).startsWith(basePath));
Assert.assertTrue(new File(treeDataStore.getFileName(datum.key)).exists());

TreeDataStoreTest.cleanUp(new File(basePath));
}

@Test
public void testRemoveDatum() throws Exception {
String basePath = System.getProperty("user.dir") + File.separator + "tree-datum-test";
TreeDataStore treeDataStore = new TreeDataStore();
ReflectionTestUtils.setField(treeDataStore, "basePath", basePath);

String namespaceId = UUID.randomUUID().toString();
String serviceName = UUID.randomUUID().toString();
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, false);

Instances value = new Instances();
Instance instance = new Instance("192.168.0.1", 8888);
value.getInstanceList().add(instance);
Datum datum = new Datum();
datum.key = key;
datum.value = value;
datum.timestamp.set(0L);

treeDataStore.write(datum);
Assert.assertTrue(new File(basePath).exists());
Assert.assertTrue(treeDataStore.getFileName(datum.key).startsWith(basePath));
Assert.assertTrue(new File(treeDataStore.getFileName(datum.key)).exists());
treeDataStore.remove(datum.key);
Assert.assertFalse(new File(treeDataStore.getFileName(datum.key)).exists());

TreeDataStoreTest.cleanUp(new File(basePath));
}

private static boolean cleanUp(File file) {
if (!file.exists()) {
return false;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File subFile : files) {
boolean success = cleanUp(subFile);
if (!success) {
return false;
}
}
} else {// is a regular file
boolean success = file.delete();
if (!success) {
return false;
}
}
if (file.isDirectory()) {
return file.delete();
} else {
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.tree.weak;
package com.alibaba.nacos.naming.consistency.weak.tree;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.naming.consistency.Datum;
Expand Down Expand Up @@ -43,8 +43,7 @@ public void testAddInstance() throws NacosException {
long timestampTwo = timestampOne + 2000L;
String namespaceId = UUID.randomUUID().toString();
String serviceName = UUID.randomUUID().toString();
boolean ephemeral = true;
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, true);

Instance one = new Instance("192.168.0.1", 8888);
Instances targetValueOne = new Instances();
Expand Down

0 comments on commit ab2d03d

Please sign in to comment.