Skip to content
This repository was archived by the owner on Apr 23, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions exhibitor-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
compile "org.apache.lucene:lucene-core:${luceneVersion}"

compile "com.sun.jersey:jersey-client:${jerseyVersion}"
compile "com.sun.jersey.contribs:jersey-multipart:${jerseyVersion}"

// if you are using Java 7 you can remove this and switch to the JDK version
compile 'org.codehaus.jsr166-mirror:jsr166y:1.7.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.netflix.exhibitor.core.entities;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ExportRequest {
private String startPath;

public ExportRequest() {
this("/");
}

public ExportRequest(String startPath) {
this.startPath = startPath;
}

public String getStartPath() {
return startPath;
}

public void setStartPath(String startPath) {
this.startPath = startPath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.netflix.exhibitor.core.importandexport;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.netflix.exhibitor.core.Exhibitor;
import com.netflix.exhibitor.core.rest.UIContext;
import com.sun.jersey.core.util.Base64;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.data.ACL;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.ObjectNode;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Exporter {
private final String startPath;
private final Exhibitor exhibitor;
private final ObjectMapper objectMapper = new ObjectMapper();

public Exporter(UIContext context, String startPath) {
this.exhibitor = context.getExhibitor();

if (Strings.isNullOrEmpty(startPath)) {
this.startPath = "/";
} else {
if (startPath.startsWith("/")) {
this.startPath = startPath;
} else {
this.startPath = "/" + startPath;
}
}
}

public String generate() throws Exception {
ArrayNode jsonArray = JsonNodeFactory.instance.arrayNode();

return convertToExportFormat(getChildren(startPath, jsonArray));
}

private String convertToExportFormat(ArrayNode jsonArray) throws JsonProcessingException {
StringBuilder sb = new StringBuilder();
sb.append("[\r\n");

Iterator<JsonNode> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
JsonNode jsonNode = iterator.next();

sb.append(convertToFlatJsonAsString(jsonNode.get("path").getTextValue(), jsonNode.get("data").getTextValue(),
jsonNode.get("acls").getElements()));

if (iterator.hasNext()) {
sb.append(",\r\n");
}
}

sb.append("\r\n]");

return sb.toString();
}

private String convertToFlatJsonAsString(String path, String data, Iterator<JsonNode> acls) throws JsonProcessingException {
ExportObject exportObject = new ExportObject()
.setPath(path)
.setData(data);

List<Acl> aclList = new ArrayList<Acl>();
while (acls.hasNext()) {
JsonNode aclNode = acls.next();
aclList.add(new Acl()
.setScheme(aclNode.get("scheme").getTextValue())
.setId(aclNode.get("id").getTextValue())
.setPerms(aclNode.get("perms").getIntValue()));
}
exportObject.setAcls(aclList);

return objectMapper.writeValueAsString(exportObject);
}


/**
* Gets the list of children for a specific path. Then for each result it calls this method again. Eventually
* we will have traversed the entire tree.
*
* @param path
* @return ArrayNode
* @throws Exception
*/
private ArrayNode getChildren(String path, ArrayNode jsonArray) throws Exception {
List<String> children = exhibitor.getLocalConnection().getChildren().forPath(path);
jsonArray.add(getNodeDetails(path));

for (String child : children) {
getChildren(ZKPaths.makePath(path, child), jsonArray);
}

return jsonArray;
}

private JsonNode getNodeDetails(String path) throws Exception {
byte[] data = exhibitor.getLocalConnection().getData().forPath(path);
if (data == null) data = new byte[0];
List<ACL> acls = exhibitor.getLocalConnection().getACL().forPath(path);

ObjectNode node = JsonNodeFactory.instance.objectNode();
node.put("path", path);
node.put("data", new String(Base64.encode(data)));

ArrayNode aclsArray = JsonNodeFactory.instance.arrayNode();
for (ACL acl : acls) {
ObjectNode aclNode = JsonNodeFactory.instance.objectNode();

aclNode.put("scheme", acl.getId().getScheme());
aclNode.put("id", acl.getId().getId());
aclNode.put("perms", acl.getPerms());

aclsArray.add(aclNode);
}

node.put("acls", aclsArray);

return node;
}

private class ExportObject {
private String path;
private String data;
private List<Acl> acls;

public String getPath() {
return path;
}

public ExportObject setPath(String path) {
this.path = path;
return this;
}

public String getData() {
return data;
}

public ExportObject setData(String data) {
this.data = data;
return this;
}

public List<Acl> getAcls() {
return acls;
}

public ExportObject setAcls(List<Acl> acls) {
this.acls = acls;
return this;
}
}

private class Acl {
private String scheme;
private String id;
private int perms;

public String getScheme() {
return scheme;
}

public Acl setScheme(String scheme) {
this.scheme = scheme;
return this;
}

public String getId() {
return id;
}

public Acl setId(String id) {
this.id = id;
return this;
}

public int getPerms() {
return perms;
}

public Acl setPerms(int perms) {
this.perms = perms;
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.netflix.exhibitor.core.importandexport;

import com.netflix.exhibitor.core.activity.ActivityLog;
import com.netflix.exhibitor.core.rest.UIContext;
import com.sun.jersey.core.util.Base64;
import org.apache.curator.framework.api.transaction.CuratorTransaction;
import org.apache.curator.framework.api.transaction.CuratorTransactionFinal;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.codehaus.jackson.JsonNode;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.util.*;

public class Importer {

private final UIContext context;

public Importer(UIContext context)
{
this.context = context;
}

/**
* Imports all of the supplied nodes, starting at the prescribed base node. Because it isn't possible to set ACLs
* on set commands, only create, and because we're using a transaction, the flow of this method is as follows.
*
* 1. If the node already exists and we're not overwriting, ignore this node and leave it alone.
* 2. If the node already exists and we are overwriting, add the set command to the transaction and save the ACL
* details. Once the transaction has been successfully committed, we then apply all the ACLs that we saved.
* 3. If the node does not exist, add the create command to the transaction including the ACLs needed.
*
* This does leave us open to a couple of possible issues. It might be possible that a node doesn't exist when we
* add it to the transaction, but it has been created by a 3rd party before we commit.
*
* It is also possible that something could go wrong when applying the ACLs to a node. Because the transaction has
* already been committed at that point, we will be left in a position where the node data has been updated but not
* the ACL.
*/
public void doImport(String basePath, boolean overwrite, Iterator<JsonNode> nodesToImport) throws Exception {
CuratorTransaction transaction = context.getExhibitor().getLocalConnection().inTransaction();
CuratorTransactionFinal curatorTransactionFinal = null;
Map<String, List<ACL>> savedACLs = new HashMap<String, List<ACL>>();
Set<String> toBeCreated = new HashSet<String>();

while (nodesToImport.hasNext()) {
JsonNode jsonNode = nodesToImport.next();
JsonNode pathNode = jsonNode.get("path");
JsonNode dataNode = jsonNode.get("data");
JsonNode aclsNode = jsonNode.get("acls");

if (pathNode == null || dataNode == null) throw new WebApplicationException(Response.Status.BAD_REQUEST);

String path = ZKPaths.makePath(basePath, pathNode.getTextValue());
byte[] data = Base64.decode(dataNode.getTextValue());
List<ACL> aclList = createACLList(aclsNode);

boolean alreadyExists = nodeAlreadyExists(path);

if (overwrite || !alreadyExists) {
if (alreadyExists) {
curatorTransactionFinal = transaction.setData().forPath(path, data).and();
savedACLs.put(path, aclList);
} else {
createParentsIfNeeded(transaction, path, aclList, toBeCreated);
curatorTransactionFinal = transaction.create().withACL(aclList).forPath(path, data).and();
toBeCreated.add(path);
}
}
}

if (curatorTransactionFinal == null) {
context.getExhibitor().getLog().add(ActivityLog.Type.INFO, "There was nothing to import");
return;
}

curatorTransactionFinal.commit();

// Finally we apply those ACLs we saved
for (Map.Entry<String, List<ACL>> entry : savedACLs.entrySet()) {
context.getExhibitor().getLocalConnection().setACL().withACL(entry.getValue()).forPath(entry.getKey());
}
}

private void createParentsIfNeeded(CuratorTransaction transaction, String path, List<ACL> acls, Set<String> toBeCreated) throws Exception {
String[] parts = path.substring(1).split("/");
String builtUpPath = "";

// We do this to parts.length - 1, because we don't want to create the final path, as that's being done in the
// calling method.

for (int i = 0; i < (parts.length - 1); i++) {
builtUpPath += "/" + parts[i];

if (!toBeCreated.contains(builtUpPath) && context.getExhibitor().getLocalConnection().checkExists().forPath(builtUpPath) == null) {
transaction.create().withACL(acls).forPath(builtUpPath, new byte[0]);
toBeCreated.add(builtUpPath);
}
}
}

private List<ACL> createACLList(JsonNode aclsNode) {
List<ACL> aclList = new ArrayList<ACL>();
if (aclsNode == null) return aclList;

Iterator<JsonNode> acls = aclsNode.getElements();

while (acls.hasNext()) {
JsonNode aclNode = acls.next();

String scheme = aclNode.get("scheme").getTextValue();
String id = aclNode.get("id").getTextValue();
int perms = aclNode.get("perms").getIntValue();

aclList.add(new ACL(perms, new Id(scheme, id)));
}

return aclList;
}

private boolean nodeAlreadyExists(String path) throws Exception {
return (context.getExhibitor().getLocalConnection().checkExists().forPath(path) != null);
}

}
Loading