Skip to content
Permalink
Browse files
CURATOR-569
- New methods to handle protected ZNode names
  • Loading branch information
chevaris authored and randgalt committed May 15, 2020
1 parent 66c81e0 commit f73b8fbb0429b7df934ee17f30f2846e7391deee
Showing 7 changed files with 263 additions and 53 deletions.
@@ -18,6 +18,9 @@
*/
package org.apache.curator.framework.api;

import java.util.UUID;

import org.apache.curator.framework.imps.ProtectedUtils;
import org.apache.zookeeper.CreateMode;

public interface CreateBuilderMain extends
@@ -27,6 +30,7 @@ public interface CreateBuilderMain extends
Compressible<CreateBackgroundModeStatACLable>,
Statable<CreateProtectACLCreateModePathAndBytesable<String>>
{

/**
* Causes any parent nodes to get created if they haven't already been
*
@@ -74,9 +78,14 @@ public interface CreateBuilderMain extends
*
* <p>
* Putting the create builder into protection mode works around this.
* The name of the node that is created is prefixed with a GUID. If node creation fails
* the normal retry mechanism will occur. On the retry, the parent path is first searched
* for a node that has the GUID in it. If that node is found, it is assumed to be the lost
* The name of the node that is created is prefixed with a 40 characters string that is the concatenation of
* <ul>
* <li>{@value ProtectedUtils#PROTECTED_PREFIX}
* <li>Canonical text representation of a random generated UUID as produced by {@link UUID#toString()}
* <li>{@value ProtectedUtils#PROTECTED_SEPARATOR}
* </ul>
* If node creation fails the normal retry mechanism will occur. On the retry, the parent path is first searched
* for a node that has previous described prefix in it. If that node is found, it is assumed to be the lost
* node that was successfully created on the first try and is returned to the caller.
* </p>
*
@@ -64,9 +64,6 @@ public class CreateBuilderImpl implements CreateBuilder, CreateBuilder2, Backgro
@VisibleForTesting
boolean failNextCreateForTesting = false;

@VisibleForTesting
static final String PROTECTED_PREFIX = "_c_";

CreateBuilderImpl(CuratorFrameworkImpl client)
{
this.client = client;
@@ -749,11 +746,6 @@ public ProtectACLCreateModePathAndBytesable<String> creatingParentContainersIfNe
};
}

private static String getProtectedPrefix(String protectedId)
{
return PROTECTED_PREFIX + protectedId + "-";
}

static <T> void backgroundCreateParentsThenNode(final CuratorFrameworkImpl client, final OperationAndData<T> mainOperationAndData, final String path, Backgrounding backgrounding, final InternalACLProvider aclProvider, final boolean createParentsAsContainers)
{
BackgroundOperation<T> operation = new BackgroundOperation<T>()
@@ -1221,13 +1213,7 @@ public String call() throws Exception
@VisibleForTesting
String adjustPath(String path) throws Exception
{
if ( protectedMode.doProtected() )
{
ZKPaths.PathAndNode pathAndNode = ZKPaths.getPathAndNode(path);
String name = getProtectedPrefix(protectedMode.protectedId()) + pathAndNode.getNode();
path = ZKPaths.makePath(pathAndNode.getPath(), name);
}
return path;
return ProtectedUtils.toProtectedZNodePath(path, protectedMode.protectedId());
}

/**
@@ -1240,7 +1226,7 @@ String adjustPath(String path) throws Exception
*/
static String findNode(final List<String> children, final String path, final String protectedId)
{
final String protectedPrefix = getProtectedPrefix(protectedId);
final String protectedPrefix = ProtectedUtils.getProtectedPrefix(protectedId);
String foundNode = Iterables.find
(
children,
@@ -30,7 +30,6 @@
class ProtectedMode
{
private final Logger log = LoggerFactory.getLogger(getClass());
private volatile boolean doProtected = false;
private volatile String protectedId = null;
private volatile long sessionId = 0;

@@ -39,7 +38,6 @@
*/
void setProtectedMode()
{
doProtected = true;
resetProtectedId();
}

@@ -56,7 +54,7 @@ void resetProtectedId()
*/
boolean doProtected()
{
return doProtected;
return (protectedId != null);
}

/**
@@ -76,7 +74,7 @@ String protectedId()
*/
void checkSetSessionId(CuratorFrameworkImpl client, CreateMode createMode) throws Exception
{
if ( doProtected && (sessionId == 0) && createMode.isEphemeral() )
if ( doProtected() && (sessionId == 0) && createMode.isEphemeral() )
{
sessionId = client.getZooKeeper().getSessionId();
}
@@ -93,7 +91,7 @@ void checkSetSessionId(CuratorFrameworkImpl client, CreateMode createMode) throw
*/
String validateFoundNode(CuratorFrameworkImpl client, CreateMode createMode, String foundNode) throws Exception
{
if ( doProtected && createMode.isEphemeral() )
if ( doProtected() && createMode.isEphemeral() )
{
long clientSessionId = client.getZooKeeper().getSessionId();
if ( this.sessionId != clientSessionId )
@@ -0,0 +1,165 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.curator.framework.imps;

import com.google.common.annotations.VisibleForTesting;
import org.apache.curator.framework.api.CreateBuilderMain;
import org.apache.curator.utils.ZKPaths;
import java.util.Optional;
import java.util.UUID;

/**
* Utility class to handle ZNode names when using {@link CreateBuilderMain#withProtection()}
*/
public final class ProtectedUtils
{

private ProtectedUtils()
{
throw new RuntimeException("Protected Utils is a helper class");
}

/**
* First 3 characters in the prefix on ZNode names when using {@link CreateBuilderMain#withProtection()}
*/
@VisibleForTesting
static final String PROTECTED_PREFIX = "_c_";

/**
* Last character used in the prefix on ZNode names when using {@link CreateBuilderMain#withProtection()}
*/
@VisibleForTesting
static final char PROTECTED_SEPARATOR = '-';

/**
* Prefix length on ZNode name when using {@link CreateBuilderMain#withProtection()}
*/
@VisibleForTesting
static final int PROTECTED_PREFIX_WITH_UUID_LENGTH = PROTECTED_PREFIX.length() + 36 // UUID canonical text representation produced by {@link UUID#toString()}
+ 1; // Separator length

/**
* Provides a prefix to be prepended to a ZNode name when protected. The method assumes that the provided string
* adjusts to the required format
*
* @param protectedId canonical text representation of a UUID
* @return string that concatenates {@value #PROTECTED_PREFIX}, the given id and {@value #PROTECTED_SEPARATOR}
*/
public static String getProtectedPrefix(final String protectedId)
{
return PROTECTED_PREFIX + protectedId + PROTECTED_SEPARATOR;
}

/** Extracts protectedId assuming provided name has a valid protected format */
private static String extractProtectedIdInternal(final String znodeName)
{
return znodeName.substring(PROTECTED_PREFIX.length(), PROTECTED_PREFIX_WITH_UUID_LENGTH - 1);
}

/**
* Utility method to determine if a given ZNode name starts with Curator's generated protected prefix.
*
* @param znodeName ZNode name
* @return {@code true} if the given ZNode name starts with Curator's generated protected prefix
*/
public static boolean isProtectedZNode(final String znodeName)
{
if ( znodeName.length() > PROTECTED_PREFIX_WITH_UUID_LENGTH && znodeName.startsWith(PROTECTED_PREFIX) && znodeName.charAt(PROTECTED_PREFIX_WITH_UUID_LENGTH - 1) == PROTECTED_SEPARATOR )
{
try
{
//noinspection ResultOfMethodCallIgnored
UUID.fromString(extractProtectedIdInternal(znodeName));
return true;
}
catch ( IllegalArgumentException e )
{
// Not an UUID
}
}
return false;
}

/**
* Utility method to remove Curator's generated protected prefix if present
*
* @param znodeName ZNode name
* @return string without Curator's generated protected prefix if present; original string if prefix not present
*/
public static String normalize(final String znodeName)
{
return isProtectedZNode(znodeName) ? znodeName.substring(PROTECTED_PREFIX_WITH_UUID_LENGTH) : znodeName;
}

/**
* Utility method to provide a path removing Curator's generated protected prefix if present in the ZNode name
*
* @param path ZNode path
* @return string without Curator's generated protected prefix if present in ZNode name; original string if prefix not present
*/
public static String normalizePath(final String path)
{
final ZKPaths.PathAndNode pathAndNode = ZKPaths.getPathAndNode(path);
final String name = pathAndNode.getNode();
return isProtectedZNode(name) ? ZKPaths.makePath(pathAndNode.getPath(), normalize(name)) : path;
}

/**
* Extracts protectedId in case the provided name is protected
*
* @param znodeName name of the ZNode
* @return Optional with protectedId if the name is protected or {@code Optional#empty()}
*/
public static Optional<String> extractProtectedId(final String znodeName)
{
return Optional.ofNullable(isProtectedZNode(znodeName) ? extractProtectedIdInternal(znodeName) : null);
}

/**
* Converts a given ZNode name to protected format
*
* @param znodeName name to be converted (e.g. 'name1')
* @param protectedId UUID canonical text representation used in protection mode (e.g. '53345f98-9423-4e0c-a7b5-9f819e3ec2e1')
* @return name with protected mode prefix (e.g. '_c_53345f98-9423-4e0c-a7b5-9f819e3ec2e1-name1')
* or the same name if protectedId is {@code null}
*/
public static String toProtectedZNode(final String znodeName, final String protectedId)
{
return (protectedId == null) ? znodeName : getProtectedPrefix(protectedId) + znodeName;
}

/**
* Converts a given path to protected format
*
* @param path complete path to be converted (e.g. '/root/path1')
* @param protectedId UUID canonical text representation used in protection mode (e.g. '53345f98-9423-4e0c-a7b5-9f819e3ec2e1')
* @return path with protected mode prefix (e.g. '/root/_c_53345f98-9423-4e0c-a7b5-9f819e3ec2e1-path1')
* or the same path if protectedId is {@code null}
*/
public static String toProtectedZNodePath(final String path, final String protectedId)
{
if ( protectedId == null )
{
return path;
}
final ZKPaths.PathAndNode pathAndNode = ZKPaths.getPathAndNode(path);
return ZKPaths.makePath(pathAndNode.getPath(), toProtectedZNode(pathAndNode.getNode(), protectedId));
}
}

0 comments on commit f73b8fb

Please sign in to comment.