Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class ContentTransformer<T> {
*/
public ContentTransformer(String path, EntryType entryType, BiFunction<Revision, T, T> transformer) {
this.path = requireNonNull(path, "path");
checkArgument(entryType == EntryType.JSON, "entryType: %s (expected: %s)", entryType, EntryType.JSON);
checkArgument(entryType == EntryType.JSON || entryType == EntryType.YAML,
"entryType: %s (expected: JSON or YAML)", entryType);
this.entryType = requireNonNull(entryType, "entryType");
this.transformer = requireNonNull(transformer, "transformer");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@
import com.linecorp.centraldogma.common.EntryType;
import com.linecorp.centraldogma.common.Revision;
import com.linecorp.centraldogma.internal.Jackson;
import com.linecorp.centraldogma.internal.Yaml;
import com.linecorp.centraldogma.server.command.ContentTransformer;

final class TransformingChangesApplier extends AbstractChangesApplier {

private final ContentTransformer<JsonNode> transformer;

TransformingChangesApplier(ContentTransformer<?> transformer) {
checkArgument(transformer.entryType() == EntryType.JSON,
"transformer: %s (expected: JSON type)", transformer);
checkArgument(transformer.entryType() == EntryType.JSON ||
transformer.entryType() == EntryType.YAML,
"transformer: %s (expected: JSON or YAML type)", transformer);
//noinspection unchecked
this.transformer = (ContentTransformer<JsonNode>) transformer;
}
Expand All @@ -55,13 +57,20 @@ int doApply(Revision headRevision, DirCache dirCache,
final DirCacheEntry oldEntry = dirCache.getEntry(changePath);
final byte[] oldContent = oldEntry != null ? reader.open(oldEntry.getObjectId()).getBytes()
: null;
final JsonNode oldJsonNode = oldContent != null ? Jackson.readTree(oldContent)
final boolean isYaml = transformer.entryType() == EntryType.YAML;
final JsonNode oldJsonNode = oldContent != null ? (isYaml ? Yaml.readTree(oldContent)
: Jackson.readTree(oldContent))
: JsonNodeFactory.instance.nullNode();
try {
final JsonNode newJsonNode = transformer.transformer().apply(headRevision, oldJsonNode.deepCopy());
requireNonNull(newJsonNode, "transformer.transformer().apply() returned null");
if (!Objects.equals(newJsonNode, oldJsonNode)) {
applyPathEdit(dirCache, new InsertJson(changePath, inserter, newJsonNode));
if (isYaml) {
applyPathEdit(dirCache, new InsertText(changePath, inserter,
Yaml.writeValueAsString(newJsonNode)));
} else {
applyPathEdit(dirCache, new InsertJson(changePath, inserter, newJsonNode));
}
return 1;
}
} catch (CentralDogmaException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ int batchUpdateTaskSize() {
return batchUpdateTasks.size();
}

private static String alternativeFileName(String fileName) {
if (fileName.endsWith(".json")) {
return fileName.substring(0, fileName.length() - 5) + ".yaml";
}
if (fileName.endsWith(".yaml")) {
return fileName.substring(0, fileName.length() - 5) + ".json";
}
return fileName;
}

void schedule(String group, String endpointName, String fileName,
LocalityLbEndpoint localityLbEndpoint, StreamObserver<?> streamObserver, boolean register) {
final EndpointIdentifier identifier = EndpointIdentifier.of(localityLbEndpoint);
Expand Down Expand Up @@ -170,11 +180,10 @@ private void flush() {
}
}

final ContentTransformer<JsonNode> transformer = new ContentTransformer<>(
fileName, EntryType.JSON, new BatchUpdateTransformer(toRegister, toDeregister));

final Repository repository = xdsResourceManager.xdsProject().repos().get(group);
repository.find(Revision.HEAD, fileName, FIND_ONE_WITHOUT_CONTENT).handle((entries, cause) -> {
final String altFileName = alternativeFileName(fileName);
repository.find(Revision.HEAD, fileName + ',' + altFileName, FIND_ONE_WITHOUT_CONTENT)
.handle((entries, cause) -> {
if (cause != null) {
copied.forEach(pendingUpdate -> pendingUpdate.streamObserver.onError(cause));
return null;
Expand All @@ -186,6 +195,11 @@ private void flush() {
copied.forEach(pendingUpdate -> pendingUpdate.streamObserver.onError(runtimeException));
return null;
}
final String resolvedFileName = entries.keySet().iterator().next();
final EntryType entryType =
resolvedFileName.endsWith(".yaml") ? EntryType.YAML : EntryType.JSON;
final ContentTransformer<JsonNode> transformer = new ContentTransformer<>(
resolvedFileName, entryType, new BatchUpdateTransformer(toRegister, toDeregister));
final String commitMessage =
"Batch update for " + endpointName + " in group " + group + ": " +
toRegister.size() + " register, " + toDeregister.size() + " deregister";
Expand Down Expand Up @@ -304,8 +318,7 @@ private static ClusterLoadAssignment.Builder toClusterLoadAssignmentBuilder(Json
final Builder clusterLoadAssignmentBuilder =
ClusterLoadAssignment.newBuilder();
try {
JSON_MESSAGE_MARSHALLER.mergeValue(Jackson.writeValueAsString(oldJsonNode),
clusterLoadAssignmentBuilder);
JSON_MESSAGE_MARSHALLER.mergeValue(oldJsonNode.traverse(), clusterLoadAssignmentBuilder);
} catch (Throwable t) {
// Should never reach here.
throw new Error();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ void removeCluster(String groupName, String path) {
}

private static String getResourceName(String groupName, String path) {
return "groups/" + groupName + path.substring(0, path.length() - 5); // Remove .json
// Remove .json or .yaml (both 5 chars)
return "groups/" + groupName + path.substring(0, path.length() - 5);
}

void removeEndpoint(String groupName, String path) {
Expand All @@ -112,11 +113,11 @@ void removeEndpoint(String groupName, String path) {
if (groupEndpoints == null) {
return;
}
// e.g. /endpoints/foo-cluster.json file with group foo -> groups/foo/clusters/foo-cluster
// e.g. /k8s/endpoints/foo-cluster.json file with group foo -> groups/foo/k8s/clusters/foo-cluster
// e.g. /endpoints/foo-cluster.json/.yaml file with group foo -> groups/foo/clusters/foo-cluster
// e.g. /k8s/endpoints/foo-cluster.json/.yaml file with group foo -> groups/foo/k8s/clusters/foo-cluster
final String clusterName =
"groups/" + groupName +
ENDPOINTS_PATTERN.matcher(path.substring(0, path.length() - 5) /* remove .json */)
ENDPOINTS_PATTERN.matcher(path.substring(0, path.length() - 5) /* remove .json or .yaml */)
.replaceFirst("/clusters/");
endpointUpdated |= groupEndpoints.remove(clusterName) != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,23 +338,23 @@ protected String pathPattern() {
}

@Override
protected void handleXdsResource(String path, String contentAsText, String groupName)
protected void handleXdsResource(String path, JsonNode content, String groupName)
throws IOException {
if (path.startsWith(CLUSTERS_DIRECTORY)) {
final Cluster.Builder builder = Cluster.newBuilder();
JSON_MESSAGE_MARSHALLER.mergeValue(contentAsText, builder);
JSON_MESSAGE_MARSHALLER.mergeValue(content.traverse(), builder);
centralDogmaXdsResources.setCluster(groupName, builder.build());
} else if (path.startsWith(ENDPOINTS_DIRECTORY) || path.startsWith(K8S_ENDPOINTS_DIRECTORY)) {
final ClusterLoadAssignment.Builder builder = ClusterLoadAssignment.newBuilder();
JSON_MESSAGE_MARSHALLER.mergeValue(contentAsText, builder);
JSON_MESSAGE_MARSHALLER.mergeValue(content.traverse(), builder);
centralDogmaXdsResources.setEndpoint(groupName, builder.build());
} else if (path.startsWith(LISTENERS_DIRECTORY)) {
final Listener.Builder builder = Listener.newBuilder();
JSON_MESSAGE_MARSHALLER.mergeValue(contentAsText, builder);
JSON_MESSAGE_MARSHALLER.mergeValue(content.traverse(), builder);
centralDogmaXdsResources.setListener(groupName, builder.build());
} else if (path.startsWith(ROUTES_DIRECTORY)) {
final RouteConfiguration.Builder builder = RouteConfiguration.newBuilder();
JSON_MESSAGE_MARSHALLER.mergeValue(contentAsText, builder);
JSON_MESSAGE_MARSHALLER.mergeValue(content.traverse(), builder);
centralDogmaXdsResources.setRoute(groupName, builder.build());
} else {
// ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ public CompletableFuture<JsonNode> listEndpoints(@Param String group) {
.thenApply(entries -> {
final ArrayNode array = JsonNodeFactory.instance.arrayNode();
for (Entry<?> entry : entries.values()) {
if (entry.type() != EntryType.JSON) {
if (entry.type() != EntryType.JSON && entry.type() != EntryType.YAML) {
continue;
}
final ObjectNode node = array.addObject();
node.put("path", entry.path());
node.put("type", "JSON");
node.put("type", entry.type().name());
node.put("revision", entry.revision().major());
}
return array;
Expand All @@ -73,7 +73,7 @@ public CompletableFuture<JsonNode> listEndpoints(@Param String group) {
*/
@Get("/xds/groups/{group}/endpoints/{*id}")
public CompletableFuture<JsonNode> getEndpoint(@Param String group, @Param String id) {
return read(group, ENDPOINTS_DIRECTORY + id + ".json");
return readByBase(group, ENDPOINTS_DIRECTORY + id);
}

/**
Expand All @@ -82,17 +82,27 @@ public CompletableFuture<JsonNode> getEndpoint(@Param String group, @Param Strin
*/
@Get("/xds/groups/{group}/k8s/endpoints/{*id}")
public CompletableFuture<JsonNode> getK8sEndpoint(@Param String group, @Param String id) {
return read(group, K8S_ENDPOINTS_DIRECTORY + id + ".json");
return readByBase(group, K8S_ENDPOINTS_DIRECTORY + id);
}

private CompletableFuture<JsonNode> read(String group, String path) {
return xdsProject.repos().get(group).get(Revision.HEAD, path).thenApply(XdsEndpointReadService::toNode);
private CompletableFuture<JsonNode> readByBase(String group, String pathBase) {
final Repository repository = xdsProject.repos().get(group);
return repository.find(Revision.HEAD, pathBase + ".json," + pathBase + ".yaml")
.thenCompose(entries -> {
if (entries.isEmpty()) {
// Delegate to get() so the caller receives a proper EntryNotFoundException.
return repository.get(Revision.HEAD, pathBase + ".json")
.thenApply(XdsEndpointReadService::toNode);
}
return CompletableFuture.completedFuture(
toNode(entries.values().iterator().next()));
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private static JsonNode toNode(Entry<?> entry) {
final ObjectNode node = JsonNodeFactory.instance.objectNode();
node.put("path", entry.path());
node.put("type", "JSON");
node.put("type", entry.type().name());
node.put("revision", entry.revision().major());
node.set("content", (JsonNode) entry.content());
return node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Function;
import java.util.regex.Pattern;

import org.curioswitch.common.protobuf.json.MessageMarshaller;
Expand Down Expand Up @@ -185,6 +186,8 @@ public <T extends Message> void push(
final JsonNode jsonNode = Jackson.readTree(jsonText);
if (create) {
change = Change.ofJsonPatch(fileName, null, jsonNode);
} else if (fileName.endsWith(".yaml")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that only json files will be pushed in this version.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct because this is for compatibility.

change = Change.ofYamlUpsert(fileName, jsonNode);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
change = Change.ofJsonUpsert(fileName, jsonNode);
}
Expand Down Expand Up @@ -236,8 +239,8 @@ public <T extends Message> void update(StreamObserver<T> responseObserver, Strin
public <T extends Message> void update(StreamObserver<T> responseObserver, String group,
String resourceName, String fileName, String summary, T resource,
Author author) {
updateOrDelete(responseObserver, group, resourceName, fileName,
() -> push(responseObserver, group, resourceName, fileName,
updateOrDelete(responseObserver, group, resourceName, fileName, resolvedFileName ->
() -> push(responseObserver, group, resourceName, resolvedFileName,
summary, resource, author, false));
}

Expand All @@ -248,10 +251,10 @@ public void delete(StreamObserver<Empty> responseObserver, String group,

public void delete(StreamObserver<Empty> responseObserver, String group,
String resourceName, String fileName, String summary, Author author) {
final Runnable deleteTask = () ->
updateOrDelete(responseObserver, group, resourceName, fileName, resolvedFileName -> () ->
commandExecutor.execute(Command.push(author, XDS_CENTRAL_DOGMA_PROJECT, group,
Revision.HEAD, summary, "", Markup.PLAINTEXT,
ImmutableList.of(Change.ofRemoval(fileName))))
ImmutableList.of(Change.ofRemoval(resolvedFileName))))
.handle((unused, cause) -> {
if (cause != null) {
responseObserver.onError(
Expand All @@ -261,14 +264,17 @@ public void delete(StreamObserver<Empty> responseObserver, String group,
responseObserver.onNext(Empty.getDefaultInstance());
responseObserver.onCompleted();
return null;
});
updateOrDelete(responseObserver, group, resourceName, fileName, deleteTask);
}));
}

public void updateOrDelete(StreamObserver<?> responseObserver, String group, String resourceName,
String fileName, Runnable task) {
String fileName, Function<String, Runnable> taskProvider) {
final Repository repository = xdsProject.repos().get(group);
repository.find(Revision.HEAD, fileName, FIND_ONE_WITHOUT_CONTENT).handle((entries, cause) -> {
// Search for both the requested filename and its alternative extension (.json ↔ .yaml)
// to support files that may have been written in either format.
final String altFileName = alternativeFileName(fileName);
repository.find(Revision.HEAD, fileName + ',' + altFileName, FIND_ONE_WITHOUT_CONTENT)
.handle((entries, cause) -> {
if (cause != null) {
responseObserver.onError(cause);
return null;
Expand All @@ -279,8 +285,19 @@ public void updateOrDelete(StreamObserver<?> responseObserver, String group, Str
.asRuntimeException());
return null;
}
task.run();
final String resolvedFileName = entries.keySet().iterator().next();
taskProvider.apply(resolvedFileName).run();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) If Runnable is invoked immediately, should we simply take Function<String, Void> or Consumer<String>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed. 😉

return null;
});
}

private static String alternativeFileName(String fileName) {
if (fileName.endsWith(".json")) {
return fileName.substring(0, fileName.length() - 5) + ".yaml";
}
if (fileName.endsWith(".yaml")) {
return fileName.substring(0, fileName.length() - 5) + ".json";
}
return fileName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Sets;
Expand Down Expand Up @@ -70,7 +71,7 @@ protected Project xdsProject() {

protected abstract String pathPattern();

protected abstract void handleXdsResource(String path, String contentAsText, String groupName)
protected abstract void handleXdsResource(String path, JsonNode content, String groupName)
throws IOException;

protected abstract void onGroupRemoved(String groupName);
Expand Down Expand Up @@ -110,13 +111,13 @@ protected void init() {
" at revision: " + normalizedRevision, cause);
}
for (Entry<?> entry : entries.values()) {
if (entry.type() != EntryType.JSON || !entry.hasContent()) {
if ((entry.type() != EntryType.JSON && entry.type() != EntryType.YAML) ||
!entry.hasContent()) {
continue;
}
final String path = entry.path();
final String contentAsText = entry.contentAsText();
try {
handleXdsResource(path, contentAsText, groupName);
handleXdsResource(path, (JsonNode) entry.content(), groupName);
} catch (Throwable t) {
logger.warn("Unexpected exception while building an xDS resource from {}.",
groupName + path, t);
Expand Down Expand Up @@ -222,8 +223,9 @@ private void handleDiff(String groupName, Revision newRevision,
final String path = change.path();
switch (change.type()) {
case UPSERT_JSON:
case UPSERT_YAML:
try {
handleXdsResource(path, change.contentAsText(), groupName);
handleXdsResource(path, (JsonNode) change.content(), groupName);
} catch (Throwable t) {
logger.warn("Unexpected exception while handling an xDS resource from {}.",
groupName + path, t);
Expand Down
Loading
Loading