diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java |
Publish
Diffstat (limited to 'vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java')
-rw-r--r-- | vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java b/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java new file mode 100644 index 00000000000..7601dd31972 --- /dev/null +++ b/vdslib/src/main/java/com/yahoo/vdslib/distribution/Group.java @@ -0,0 +1,300 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vdslib.distribution; + +import java.util.*; +import java.text.ParseException; + +/** + * Represent a group in the tree structure of groups in hierarchical setup of VDS nodes. + */ +public class Group implements Comparable<Group> { + + private String name; + private Group parent = null; + private int index; + private int distributionHash; + private Distribution distribution = null; + private double capacity; + private Map<Integer, Group> subgroups; + private List<ConfiguredNode> nodes; + + public Group(int index, String name) { + this.name = name; + this.index = index; + this.distributionHash = 0; + this.distribution = null; + this.capacity = 1; + this.nodes = new ArrayList<>(); + this.subgroups = null; + } + + public Group(int index, String name, Distribution d) { + this.name = name; + this.index = index; + this.distributionHash = 0; + this.distribution = d; + this.capacity = 1; + this.nodes = null; + this.subgroups = new TreeMap<>(); + } + + private String getPathWithSeparator(String separator) { + if (parent != null) { + final String prefix = parent.getPathWithSeparator(separator); + return prefix.isEmpty() ? name : prefix + separator + name; + } else { + return ""; + } + } + + public String getPath() { + return getPathWithSeparator("."); + } + + public String getUnixStylePath() { + return "/" + getPathWithSeparator("/"); + } + + @Override + public int compareTo(Group o) { + return new Integer(index).compareTo(o.getIndex()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if ( ! (o instanceof Group)) { return false; } + Group other = (Group) o; + if ( ! name.equals(other.name) + || index != other.index + || (distribution == null ^ other.distribution == null) + || (distribution != null && ! distribution.equals(other.distribution)) + || Math.abs(capacity - other.capacity) > 0.0000001 + || (subgroups == null ^ other.subgroups == null) + || (subgroups != null && !subgroups.equals(other.subgroups)) + || (nodes == null ^ other.nodes == null) + || (nodes != null && !nodes.equals(other.nodes))) + { + return false; + } + return true; + } + + @Override + public int hashCode() { + return name.hashCode() + + 17 * index + + 23 * distribution.hashCode() + + 43 * subgroups.hashCode() + + 47 * nodes.hashCode(); + + } + + @Override + public String toString() { + return toString(""); + } + + public String toString(String indent) { + StringBuffer sb = new StringBuffer(); + sb.append("Group(name: ").append(name).append(", index: ").append(index); + if (distribution != null) sb.append(", distribution: ").append(distribution); + if (Math.abs(capacity - 1.0) > 0.0000001) sb.append(", capacity: ").append(capacity); + if (nodes != null) { + sb.append(", nodes( "); + for (ConfiguredNode node : nodes) { + sb.append(node.index()).append(' '); + } + sb.append(")"); + } + if (subgroups != null) { + sb.append(", subgroups: ").append(subgroups.size()); + } + sb.append(") {"); + if (subgroups != null && subgroups.size() > 0) { + for (Group g : subgroups.values()) { + sb.append("\n").append(indent).append(" "); + sb.append(g.toString(indent + " ")); + } + } + sb.append("\n").append(indent).append("}"); + return sb.toString(); + } + + public void addSubGroup(Group g) { + if (distribution == null) { + throw new IllegalStateException("Cannot add sub groups to a node without distribution set."); + } + if (subgroups.containsKey(g.getIndex())) { + throw new IllegalStateException("A subgroup with index " + g.getIndex() + " already exist."); + } + if (nodes != null) { + throw new IllegalStateException("Cannot add subgroup to leaf group with nodes"); + } + g.parent = this; + subgroups.put(g.getIndex(), g); + } + + public void setCapacity(double c) { capacity = c; } + + public void setNodes(List<ConfiguredNode> nodes) { + if (distribution != null) { + throw new IllegalStateException("Cannot add nodes to non-leaf group with distribution set"); + } + if (subgroups != null) { + throw new IllegalStateException("Cannot add nodes to group with children"); + } + this.nodes = new ArrayList<>(nodes); + Collections.sort(this.nodes); + } + + public String getName() { return name; } + public int getIndex() { return index; } + public List<ConfiguredNode> getNodes() { return Collections.unmodifiableList(nodes); } + public Map<Integer, Group> getSubgroups() { return Collections.unmodifiableMap(subgroups); } + public double getCapacity() { return capacity; } + public int getDistributionHash() { return distributionHash; } + public boolean isLeafGroup() { return (distribution == null); } + public Distribution getDistribution() { return distribution; } + + /** + * The distribution hashes is hopefully unique numbers for each group that is used to adjust the seed generated + * for groups. This is called by Distribution during configuration on the root node. It recursively generates all hashes. + */ + void calculateDistributionHashValues() { + calculateDistributionHashValues(0x8badf00d); + } + + private void calculateDistributionHashValues(int parentHash) { + distributionHash = parentHash ^ (1664525 * index + 1013904223); + if (subgroups == null) return; + for (Map.Entry<Integer, Group> entry : subgroups.entrySet()) { + entry.getValue().calculateDistributionHashValues(distributionHash); + } + } + + public Group getGroupForNode(int index) { + if (nodes != null) { + for (ConfiguredNode node : nodes) { + if (node.index() == index) { + return this; + } + } + } + + if (subgroups != null) { + for (Group group : subgroups.values()) { + Group retVal = group.getGroupForNode(index); + if (retVal != null) { + return retVal; + } + } + } + + return null; + } + + /** + * The distribution class keeps precalculated arrays for distributions for all legal redundancies. The class is + * immutable, such that it can be returned safely out from the group object. + */ + public static class Distribution { + + private final int[] distributionSpec; + private final int[][] preCalculatedResults; + + public Distribution(String serialized, int maxRedundancy) throws ParseException { + StringTokenizer st = new StringTokenizer(serialized, "|"); + // Create the distribution spec + int[] distributionSpec = new int[st.countTokens()]; + for (int i=0; i<distributionSpec.length; ++i) { + String token = st.nextToken(); + try{ + distributionSpec[i] = (token.equals("*") ? 0 : Integer.valueOf(token)); + } catch (NumberFormatException e) { + throw new ParseException("Illegal distribution spec \"" + serialized + "\". Copy counts must be integer values in the range 1-255.", i); + } + if (!token.equals("*") && distributionSpec[i] == 0) { + throw new ParseException("Illegal distribution spec \"" + serialized + "\". Copy counts must be in the range 1-255.", i); + } + } + // Verify sanity of the distribution spec + int firstAsterix = distributionSpec.length; + for (int i=0; i<distributionSpec.length; ++i) { + if (i > firstAsterix) { + if (distributionSpec[i] != 0) { + throw new ParseException("Illegal distribution spec \"" + serialized + "\". Asterix specification must be tailing the specification.", i); + } + continue; + } + if (i < firstAsterix && distributionSpec[i] == 0) { + firstAsterix = i; + continue; + } + if (distributionSpec[i] <= 0 || distributionSpec[i] >= 256) { + throw new ParseException("Illegal distribution spec \"" + serialized + "\". Copy counts must be in the range 1-255.", i); + } + } + this.distributionSpec = distributionSpec; + // Create the pre calculated results + if (maxRedundancy <= 0 || maxRedundancy > 255) throw new IllegalArgumentException("The max redundancy must be a positive number in the range 1-255."); + int asterixCount = distributionSpec.length - firstAsterix; + int[][] preCalculations = new int[maxRedundancy + 1][]; + for (int i=1; i<=maxRedundancy; ++i) { + List<Integer> spec = new ArrayList<Integer>(); + for (int j=0; j<distributionSpec.length; ++j) { + spec.add(distributionSpec[j]); + } + int remainingRedundancy = i; + for (int j=0; j<firstAsterix; ++j) { + spec.set(j, Math.min(remainingRedundancy, spec.get(j))); + remainingRedundancy -= spec.get(j); + } + int divided = remainingRedundancy / asterixCount; + remainingRedundancy = remainingRedundancy % asterixCount; + for (int j=firstAsterix; j<spec.size(); ++j) { + spec.set(j, divided + (j - firstAsterix < remainingRedundancy ? 1 : 0)); + } + while (spec.get(spec.size() - 1) == 0) { + spec.remove(spec.size() - 1); + } + preCalculations[i] = new int[spec.size()]; + Collections.sort(spec); + for (int j=0; j<spec.size(); ++j) preCalculations[i][j] = spec.get(spec.size() - 1 - j); + } + this.preCalculatedResults = preCalculations; + } + + public int[] getRedundancyArray(int redundancy) { + if (redundancy == 0 || redundancy >= preCalculatedResults.length) { + throw new IllegalArgumentException("Can only retrieve redundancy arrays in the inclusive range 1-" + (preCalculatedResults.length - 1) + "."); + } + return preCalculatedResults[redundancy]; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if ( ! (o instanceof Distribution)) return false; + Distribution other = (Distribution) o; + return (distributionSpec == other.distributionSpec && preCalculatedResults.length == other.preCalculatedResults.length); + } + + @Override + public int hashCode() { + return Arrays.hashCode(distributionSpec) + 13 * preCalculatedResults.length; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + for (int i=0; i<distributionSpec.length; ++i) { + if (i != 0) sb.append('|'); + if (distributionSpec[i] == 0) sb.append('*'); + else sb.append(distributionSpec[i]); + } + return sb.toString(); + } + } + +} |