All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bboxdb.distribution.zookeeper.DistributionRegionAdapter Maven / Gradle / Ivy

There is a newer version: 1.0.0-rc3
Show newest version
/*******************************************************************************
 *
 *    Copyright (C) 2015-2018 the BBoxDB project
 *  
 *    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 org.bboxdb.distribution.zookeeper;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

import org.apache.zookeeper.Watcher;
import org.bboxdb.commons.InputParseException;
import org.bboxdb.commons.MathUtil;
import org.bboxdb.commons.math.Hyperrectangle;
import org.bboxdb.distribution.membership.BBoxDBInstance;
import org.bboxdb.distribution.partitioner.DistributionRegionState;
import org.bboxdb.distribution.region.DistributionRegion;
import org.bboxdb.misc.BBoxDBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DistributionRegionAdapter {

	/**
	 * The zookeeper client
	 */
	private ZookeeperClient zookeeperClient;
	
	/**
	 * The logger
	 */
	private final static Logger logger = LoggerFactory.getLogger(DistributionRegionAdapter.class);


	public DistributionRegionAdapter(final ZookeeperClient zookeeperClient) {
		this.zookeeperClient = zookeeperClient;
	}
	

	/**
	 * Get the bounding box position for a given path
	 * @param path
	 * @return
	 * @throws ZookeeperException
	 * @throws ZookeeperNotFoundException 
	 */
	public Hyperrectangle getBoundingBoxForPath(final String path) throws ZookeeperException, ZookeeperNotFoundException  {
		
		final String splitPathName = path + "/" + ZookeeperNodeNames.NAME_BOUNDINGBOX;
		String boundingBoxString = null;
		
		try {			
			boundingBoxString = zookeeperClient.readPathAndReturnString(splitPathName);
			return new Hyperrectangle(boundingBoxString);
		} catch (NumberFormatException e) {
			throw new ZookeeperException("Unable to parse bounding box '" + boundingBoxString + "' for " + splitPathName);
		}		
	}
	
	/**
	 * Set the split position for the given path
	 * @param path
	 * @param position
	 * @throws ZookeeperException 
	 */
	public void setBoundingBoxForPath(final String path, final Hyperrectangle boundingBox) 
			throws ZookeeperException {
		
		final String boundingBoxString = boundingBox.toCompactString();
		zookeeperClient.createPersistentNode(path + "/" + ZookeeperNodeNames.NAME_BOUNDINGBOX, 
				boundingBoxString.getBytes());		
	}
	
	/**
	 * Get the state for a given path - version without a watcher
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public DistributionRegionState getStateForDistributionRegion(final String path) 
			throws ZookeeperException, ZookeeperNotFoundException {
		
		return getStateForDistributionRegion(path, null);
	}
	
	/**
	 * Get the state for a given path
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public DistributionRegionState getStateForDistributionRegion(final String path, 
			final Watcher callback) throws ZookeeperException, ZookeeperNotFoundException {
		
		final String statePath = path + "/" + ZookeeperNodeNames.NAME_REGION_STATE;
		final String state = zookeeperClient.readPathAndReturnString(statePath, callback);
		return DistributionRegionState.fromString(state);
	}
	
	/**
	 * Set the given region to full (if possible)
	 * @param region
	 * @return
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public boolean setToFull(final DistributionRegion region) 
			throws ZookeeperException, ZookeeperNotFoundException {
		
		logger.debug("Set state for {} to full", region.getIdentifier());
		
		final String zookeeperPath = getZookeeperPathForDistributionRegionState(region);
				
		final DistributionRegionState oldState = getStateForDistributionRegion(region);
		
		if(oldState != DistributionRegionState.ACTIVE) {
			logger.debug("Old state is not active (old value {})" , oldState);
			return false;
		}
		
		final boolean result = zookeeperClient.testAndReplaceValue(zookeeperPath, 
				DistributionRegionState.ACTIVE.getStringValue(), 
				DistributionRegionState.ACTIVE_FULL.getStringValue());
		
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, zookeeperPath);
		
		return result;
	}
	
	/**
	 * Set the given region to full (if possible)
	 * @param region
	 * @return
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public boolean setToSplitMerging(final DistributionRegion region) 
			throws ZookeeperException, ZookeeperNotFoundException {
		
		logger.debug("Set state for {} to merging", region.getIdentifier());
		
		final String zookeeperPath = getZookeeperPathForDistributionRegionState(region);
				
		final DistributionRegionState oldState = getStateForDistributionRegion(region);
		
		if(oldState != DistributionRegionState.SPLIT) {
			logger.debug("Old state is not active (old value {})" , oldState);
			return false;
		}
		
		final boolean result = zookeeperClient.testAndReplaceValue(zookeeperPath, 
				DistributionRegionState.SPLIT.getStringValue(), 
				DistributionRegionState.SPLIT_MERGING.getStringValue());
		
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, zookeeperPath);

		return result;
	}
	
	/**
	 * Get the state for a given path - without watcher
	 * @return 
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public DistributionRegionState getStateForDistributionRegion(final DistributionRegion region) 
			throws ZookeeperException, ZookeeperNotFoundException  {
		
		return getStateForDistributionRegion(region, null);
	}

	/**
	 * Get the state for a given path
	 * @return 
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public DistributionRegionState getStateForDistributionRegion(final DistributionRegion region, 
			final Watcher callback) throws ZookeeperException, ZookeeperNotFoundException  {
		
		final String path = getZookeeperPathForDistributionRegion(region);
		return getStateForDistributionRegion(path, callback);
	}
	
	/**
	 * Set the state for a given path
	 * @param path
	 * @param state
	 * @throws ZookeeperException 
	 */
	public void setStateForDistributionGroup(final String path, final DistributionRegionState state) 
			throws ZookeeperException  {
		
		logger.debug("Set state {} for path {}", state, path);
		
		final String statePath = path + "/" + ZookeeperNodeNames.NAME_REGION_STATE;
		zookeeperClient.setData(statePath, state.getStringValue());
		
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, path);
	}
	
	/**
	 * Set the state for a given distribution region
	 * @param region
	 * @param state
	 * @throws ZookeeperException
	 */
	public void setStateForDistributionRegion(final DistributionRegion region, 
			final DistributionRegionState state) throws ZookeeperException  {
		
		final String path = getZookeeperPathForDistributionRegion(region);
		setStateForDistributionGroup(path, state);
		
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, path);
	}
	
	/**
	 * Get the path for the distribution region state
	 * @param region
	 * @return
	 */
	private String getZookeeperPathForDistributionRegionState(final DistributionRegion region) {
		
		return getZookeeperPathForDistributionRegion(region) 
				+ "/" + ZookeeperNodeNames.NAME_REGION_STATE;
	}
	
	/**
	 * Get the node for the given zookeeper path
	 * @param distributionRegion
	 * @param path
	 * @return
	 */
	public DistributionRegion getNodeForPath(final DistributionRegion distributionRegion, 
			final String path) {
		
		final String name = distributionRegion.getDistributionGroupName();
		final String distributionGroupPath 
			= zookeeperClient.getDistributionGroupAdapter().getDistributionGroupRootElementPath(name);
		
		if(! path.startsWith(distributionGroupPath)) {
			throw new IllegalArgumentException("Path " + path + " does not start with " + distributionGroupPath);
		}
		
		final StringBuilder sb = new StringBuilder(path);
		sb.delete(0, distributionGroupPath.length());
		
		DistributionRegion resultElement = distributionRegion;
		final StringTokenizer tokenizer = new StringTokenizer(sb.toString(), "/");
		
		while(tokenizer.hasMoreTokens()) {
			
			// Element is removed
			if(resultElement == null) {
				return null;
			}
			
			final String token = tokenizer.nextToken();
			if(! token.startsWith(ZookeeperNodeNames.NAME_CHILDREN)) {
				throw new IllegalArgumentException("Unable to decode " + sb);
			}
			
			final String split[] = token.split("-");
			final int childNumber = Integer.parseInt(split[1]);
			
			resultElement = resultElement.getChildNumber(childNumber);			
		}
		
		return resultElement;
	}
	

	/**
	 * Delete the given child
	 * @param region
	 * @throws BBoxDBException 
	 * @throws ZookeeperException 
	 */
	public void deleteChild(final DistributionRegion region) throws ZookeeperException {
		
		assert(region.isLeafRegion()) : "Region is not a leaf region: " + region;
		
		final String zookeeperPath = getZookeeperPathForDistributionRegion(region);
		
		zookeeperClient.deleteNodesRecursive(zookeeperPath);
		
		if(! region.isRootElement()) {
			final String parentPath = getZookeeperPathForDistributionRegion(region.getParent());
			NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, parentPath);
		}
	}
	
	/**
	 * Get the zookeeper path for a distribution region
	 * @param distributionRegion
	 * @return
	 */
	public String getZookeeperPathForDistributionRegion(final DistributionRegion distributionRegion) {
		
		final StringBuilder sb = new StringBuilder();
		
		DistributionRegion tmpRegion = distributionRegion;
		
		if(tmpRegion != null) {
			while(! tmpRegion.isRootElement()) {
				sb.insert(0, "/" + ZookeeperNodeNames.NAME_CHILDREN + tmpRegion.getChildNumberOfParent());	
				tmpRegion = tmpRegion.getParent();
			}
		}
		
		final String name = distributionRegion.getDistributionGroupName();
		final DistributionGroupAdapter distributionGroupAdapter = zookeeperClient.getDistributionGroupAdapter();
		sb.insert(0, distributionGroupAdapter.getDistributionGroupRootElementPath(name));
		return sb.toString();
	}
	
	
	
	/**
	 * Get the systems for the distribution region
	 * @param region
	 * @param callback 
	 * @return
	 * @throws ZookeeperException 
	 * @throws ZookeeperNotFoundException 
	 */
	public Collection getSystemsForDistributionRegion(final DistributionRegion region) 
			throws ZookeeperException, ZookeeperNotFoundException {
	
		final String path = getZookeeperPathForDistributionRegion(region) 
				+ "/" + ZookeeperNodeNames.NAME_SYSTEMS;
		
		final List children = zookeeperClient.getChildren(path);
		
		return children.stream()
			.map(c -> new BBoxDBInstance(c))
			.collect(Collectors.toList());
	}
	
	/**
	 * Add a system to a distribution region
	 * @param region
	 * @param system
	 * @throws ZookeeperException 
	 */
	public void addSystemToDistributionRegion(final String regionPath, 
			final BBoxDBInstance system) throws ZookeeperException {
		
		if(system == null) {
			throw new IllegalArgumentException("Unable to add system with value null");
		}
	
		final String systemsPath = regionPath + "/" + ZookeeperNodeNames.NAME_SYSTEMS;
		final String instancePath = systemsPath + "/" + system.getStringValue();

		logger.debug("Register system under systems node: {}", systemsPath);
		
		zookeeperClient.replacePersistentNode(instancePath, "".getBytes());
		
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, regionPath);
	}
	
	/**
	 * Set the checkpoint for the distribution region and system
	 * @param region
	 * @param system
	 * @throws ZookeeperException
	 * @throws InterruptedException 
	 */
	public void setCheckpointForDistributionRegion(final DistributionRegion region, 
			final BBoxDBInstance system, final long checkpoint) 
					throws ZookeeperException, InterruptedException {
		
		if(system == null) {
			throw new IllegalArgumentException("Unable to add system with value null");
		}
		
		final String path = getZookeeperPathForDistributionRegion(region) 
				+ "/" + ZookeeperNodeNames.NAME_SYSTEMS + "/" + system.getStringValue();
		
		logger.debug("Set checkpoint for: {} to {}", path, checkpoint);
		
		if(! zookeeperClient.exists(path)) {
			throw new ZookeeperException("Path " + path + " does not exists");
		}
		
		zookeeperClient.setData(path, Long.toString(checkpoint));
	}
	
	/**
	 * Get the checkpoint for the distribution region and system
	 * @param region
	 * @param system
	 * @return 
	 * @throws ZookeeperException
	 */
	public long getCheckpointForDistributionRegion(final DistributionRegion region, 
			final BBoxDBInstance system) throws ZookeeperException {
		
		if(system == null) {
			throw new IllegalArgumentException("Unable to add system with value null");
		}
		
		try {
			final String path = getZookeeperPathForDistributionRegion(region) 
					+ "/" + ZookeeperNodeNames.NAME_SYSTEMS + "/" + system.getStringValue();
		
			if(! zookeeperClient.exists(path)) {
				throw new ZookeeperException("Path " + path + " does not exists");
			}

			final String checkpointString = zookeeperClient.getData(path);
			
			if("".equals(checkpointString)) {
				return -1;
			}
			
			return Long.parseLong(checkpointString);
		} catch (NumberFormatException e) {
			throw new ZookeeperException(e);
		}
	}
			
	/**
	 * Delete a system to a distribution region
	 * @param region
	 * @param system
	 * @return 
	 * @throws ZookeeperException 
	 */
	public boolean deleteSystemFromDistributionRegion(final DistributionRegion region, 
			final BBoxDBInstance system) throws ZookeeperException {
		
		if(system == null) {
			throw new IllegalArgumentException("Unable to delete system with value null");
		}

		final String path = getZookeeperPathForDistributionRegion(region) + "/" 
				+ ZookeeperNodeNames.NAME_SYSTEMS + "/" + system.getStringValue();
		
		if(! zookeeperClient.exists(path)) {
			return false;
		}
		
		zookeeperClient.deleteNodesRecursive(path);
	
		return true;
	}
	

	/**
	 * Update the region statistics
	 * @param region
	 * @param system
	 * @param size
	 * @param tuple
	 * @return
	 * @throws ZookeeperException
	 */
	public void updateRegionStatistics(final DistributionRegion region, 
			final BBoxDBInstance system, final long size, final long tuple) throws ZookeeperException {
		
		if(system == null) {
			throw new IllegalArgumentException("Unable to add system with value null");
		}
		
		logger.debug("Update region statistics for {} / {}", region.getDistributionGroupName(), system);
	
		final String path = getZookeeperPathForDistributionRegion(region) 
				+ "/" + ZookeeperNodeNames.NAME_STATISTICS + "/" + system.getStringValue();
		
		zookeeperClient.createDirectoryStructureRecursive(path);
		
		final String sizePath = path + "/" + ZookeeperNodeNames.NAME_STATISTICS_TOTAL_SIZE;
		zookeeperClient.replacePersistentNode(sizePath, Long.toString(size).getBytes());
		
		final String tuplePath = path + "/" + ZookeeperNodeNames.NAME_STATISTICS_TOTAL_TUPLES;
		zookeeperClient.replacePersistentNode(tuplePath, Long.toString(tuple).getBytes());
	}
	
	/**
	 * Get the statistics for a given region
	 * @param region
	 * @return
	 * @throws ZookeeperNotFoundException 
	 * @throws ZookeeperException 
	 */
	public Map> getRegionStatistics(final DistributionRegion region) 
			throws ZookeeperException {
		
		final Map> result = new HashMap<>();
		
		logger.debug("Get statistics for {}", region.getDistributionGroupName());
				
		final String statisticsPath = getZookeeperPathForDistributionRegion(region) 
				+ "/" + ZookeeperNodeNames.NAME_STATISTICS;
		
		try {
			final List children = zookeeperClient.getChildren(statisticsPath);
			processStatistics(result, statisticsPath, children);
		} catch (ZookeeperNotFoundException e) {
			// No statistics are found, return empty result
		}		
		
		return result;
	}

	/**
	 * @param result
	 * @param statisticsPath
	 * @param childs
	 * @throws ZookeeperException
	 */
	private void processStatistics(final Map> result, 
			final String statisticsPath, final List childs) throws ZookeeperException {
		
		for(final String system : childs) {
			final String path = statisticsPath + "/" + system;
		
			final Map systemMap = new HashMap<>();
			
			try {
				final String sizePath = path + "/" + ZookeeperNodeNames.NAME_STATISTICS_TOTAL_SIZE;
				if(zookeeperClient.exists(sizePath)) {
					final String sizeString = zookeeperClient.readPathAndReturnString(sizePath);
					final long size = MathUtil.tryParseLong(sizeString, () -> "Unable to parse " + sizeString);
					systemMap.put(ZookeeperNodeNames.NAME_STATISTICS_TOTAL_SIZE, size);
				}
				
				final String tuplePath = path + "/" + ZookeeperNodeNames.NAME_STATISTICS_TOTAL_TUPLES;
				if(zookeeperClient.exists(tuplePath)) {
					final String tuplesString = zookeeperClient.readPathAndReturnString(tuplePath);
					final long tuples = MathUtil.tryParseLong(tuplesString, () -> "Unable to parse " + tuplesString);
					systemMap.put(ZookeeperNodeNames.NAME_STATISTICS_TOTAL_TUPLES, tuples);
				}
				
				result.put(new BBoxDBInstance(system), systemMap);
			} catch (InputParseException | ZookeeperNotFoundException e) {
				logger.error("Unable to read statistics", e);
			}
		}
	}
	
	/**
	 * Delete the statistics for a given region
	 * @param region
	 * @return
	 * @throws ZookeeperNotFoundException 
	 * @throws ZookeeperException 
	 */
	public void deleteRegionStatistics(final DistributionRegion region) 
			throws ZookeeperException, ZookeeperNotFoundException {
				
		logger.debug("Delete statistics for {}", region.getDistributionGroupName());
				
		final String statisticsPath = getZookeeperPathForDistributionRegion(region) 
				+ "/" + ZookeeperNodeNames.NAME_STATISTICS;
		
		zookeeperClient.deleteNodesRecursive(statisticsPath);
	}
	
	/**
	 * Allocate the given list of systems to a region
	 * @param region
	 * @param allocationSystems
	 * @throws ZookeeperException
	 */
	public void allocateSystemsToRegion(final String regionPath, final Set allocationSystems)
			throws ZookeeperException {
		
		final List systemNames = allocationSystems.stream()
				.map(s -> s.getStringValue())
				.collect(Collectors.toList());
		
		logger.info("Allocating region {} to {}", regionPath, systemNames);
		
		// Resource allocation successfully, write data to zookeeper
		for(final BBoxDBInstance instance : allocationSystems) {
			addSystemToDistributionRegion(regionPath, instance);
		}
	}
	
	/**
	 * Get the merge path
	 * @param region
	 * @return
	 */
	private String getMergePath(final DistributionRegion region) {
		final String path = getZookeeperPathForDistributionRegion(region);
		return path + "/" + ZookeeperNodeNames.NAME_MERGING_SUPPORTED;
	}
	
	/**
	 * Set the merging supported flag
	 * @param region
	 * @param suppported
	 * @throws ZookeeperException 
	 */
	public void setMergingSupported(final DistributionRegion region, final boolean suppported)
			throws ZookeeperException {
		
		final String pathMerge = getMergePath(region);
		final String value = Boolean.toString(suppported);
		
		zookeeperClient.replacePersistentNode(pathMerge, value.getBytes());
	}
	
	/**
	 * Is merging of the region supported?
	 * @param region
	 * @return
	 * @throws ZookeeperException
	 */
	public boolean isMergingSupported(final DistributionRegion region) throws BBoxDBException {
		final String pathMerge = getMergePath(region);
		
		try {
			final String value = zookeeperClient.readPathAndReturnString(pathMerge);
			return Boolean.parseBoolean(value);
		} catch (ZookeeperNotFoundException e) {
			// Not configured default: true
			return true;
		} catch (ZookeeperException e) {
			throw new BBoxDBException(e);
		}
	}
	
	/**
	 * Create a new child
	 * @param childNumber 
	 * @param leftBoundingBox 
	 * @param path
	 * @return 
	 * @throws ZookeeperException
	 */
	public String createNewChild(final String parentPath, final long childNumber, 
			final Hyperrectangle boundingBox, final String distributionGroupName) throws ZookeeperException {

		final String childPath = parentPath + "/" + ZookeeperNodeNames.NAME_CHILDREN + childNumber;
		logger.info("Creating: {}", childPath);
		
		if(zookeeperClient.exists(childPath)) {
			throw new ZookeeperException("Child already exists: " + childPath);
		}

		zookeeperClient.createPersistentNode(childPath, "".getBytes());
		
		final DistributionGroupAdapter distributionGroupAdapter 
			= zookeeperClient.getDistributionGroupAdapter();
		
		final int namePrefix 
			= distributionGroupAdapter.getNextTableIdForDistributionGroup(distributionGroupName);
		
		zookeeperClient.createPersistentNode(childPath + "/" + ZookeeperNodeNames.NAME_NAMEPREFIX, 
				Integer.toString(namePrefix).getBytes());
		
		logger.info("Set {} to {}", childPath, namePrefix);
		
		zookeeperClient.createPersistentNode(childPath + "/" + ZookeeperNodeNames.NAME_SYSTEMS, 
				"".getBytes());
		
		setBoundingBoxForPath(childPath, boundingBox);
				
		zookeeperClient.createPersistentNode(childPath + "/" + ZookeeperNodeNames.NAME_REGION_STATE, 
				DistributionRegionState.CREATING.getStringValue().getBytes());
			
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, childPath);
		NodeMutationHelper.markNodeMutationAsComplete(zookeeperClient, parentPath);

		return childPath;
	}
	
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy