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

org.apache.brooklyn.util.collections.QuorumCheck Maven / Gradle / Ivy

Go to download

Utility classes and methods developed for Brooklyn but not dependendent on Brooklyn or much else

There is a newer version: 1.1.0
Show newest version
/*
 * 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
 *
 *     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.apache.brooklyn.util.collections;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.brooklyn.util.yaml.Yamls;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;

/**
 * For checking if a group/cluster is quorate. That is, whether the group has sufficient
 * healthy members.
 */
public interface QuorumCheck {

    /**
     * @param sizeHealthy Number of healthy members
     * @param totalSize   Total number of members one would expect to be healthy (i.e. ignoring stopped members)
     * @return            Whether this group is healthy
     */
    public boolean isQuorate(int sizeHealthy, int totalSize);

    public static class QuorumChecks {
        /**
         * Checks that all members that should be up are up (i.e. ignores stopped nodes).
         */
        public static QuorumCheck all() {
            return new NumericQuorumCheck(0, 1.0, false, "all");
        }
        /**
         * Checks all members that should be up are up, and that there is at least one such member.
         */
        public static QuorumCheck allAndAtLeastOne() {
            return new NumericQuorumCheck(1, 1.0, false, "allAndAtLeastOne");
        }
        /**
         * Requires at least one member that should be up.
         */
        public static QuorumCheck atLeastOne() {
            return new NumericQuorumCheck(1, 0.0, false, "atLeastOne");
        }
        /**
         * Requires at least one member to be up if the total size is non-zero.
         * i.e. okay if empty, or if non-empty and something is healthy, but not okay if not-empty and nothing is healthy.
         * "Empty" means that no members are supposed to be up  (e.g. there may be stopped members).
         */
        public static QuorumCheck atLeastOneUnlessEmpty() {
            return new NumericQuorumCheck(1, 0.0, true, "atLeastOneUnlessEmpty");
        }
        /**
         * Always "healthy"
         */
        public static QuorumCheck alwaysTrue() {
            return new NumericQuorumCheck(0, 0.0, true, "alwaysHealthy");
        }
        
        public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
            return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
        }
        
        /** See {@link QuorumChecks#newLinearRange(String,String)} */
        public static QuorumCheck newLinearRange(String range) {
            return newLinearRange(range, null);
        }
        
        /** Given a JSON representation of a list of points (where a point is a list of 2 numbers),
         * with the points in increasing x-coordinate value,
         * this constructs a quorum check which does linear interpolation on those coordinates,
         * with extensions to either side.
         * The x-coordinate is taken as the total size, and the y-coordinate as the minimum required size.
         * 

* It sounds complicated but it gives a very easy and powerful way to define quorum checks. * For instance: *

* [[0,0],[1,1]] says that if 0 items are expected, at least 0 is required; * if 1 is expected, 1 is required; and by extension if 10 are expected, 10 are required. * In other words, this is the same as {@link #all()}. *

* [[0,1],[1,1],[2,2]] is the same as the previous for x (number expected) greater-than or equal to 1; * but if 0 is expected, 1 is required, and so it fails when 0 are present. * In other words, {@link #allAndAtLeastOne()}. *

* [[5,5],[10,10],[100,70],[200,140]] has {@link #all()} behavior up to 10 expected * (line extended to the left, for less than 5); but then gently tapers off to requiring only 70% at 100 * (with 30 of 40 = 75% required at that intermediate point along the line [[10,10],[100,70]]); * and then from 100 onwards it is a straight 70%. *

* The type of linear regression described in the last example is quite useful in practise, * to be stricter for smaller clusters (or possibly more lax for small values in some cases, * such as when tolerating dangling references during rebind). */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static QuorumCheck newLinearRange(String range, String name) { Object input = Iterables.getOnlyElement( Yamls.parseAll(range) ); if (input instanceof Iterable) return LinearRangeQuorumCheck.of(name, (Iterable)input); throw new IllegalArgumentException("Invalid input to linear range quorum check; should be a list of points (not '"+range+"')"); } private static final List NAMED_CHECKS = MutableList .of(all(), allAndAtLeastOne(), atLeastOne(), atLeastOneUnlessEmpty(), alwaysTrue()); public static QuorumCheck of(String nameOrRange) { if (nameOrRange==null) return null; for (QuorumCheck qc: NAMED_CHECKS) { if (qc instanceof NumericQuorumCheck) { if (Objects.equal(nameOrRange, ((NumericQuorumCheck)qc).getName())) return qc; } } // parse YAML Object input = Iterables.getOnlyElement( Yamls.parseAll(nameOrRange) ); if (input instanceof Collection) return of((Collection)input); if (input instanceof Integer || input instanceof Long) return of((int)((Number)input)); // TODO also accept "50%", and "50%,1" throw new IllegalArgumentException("Unknown quorum check format '"+input+"'"); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static QuorumCheck of(Collection pointsForLinearRange) { return LinearRangeQuorumCheck.of(null, (Iterable)pointsForLinearRange); } public static QuorumCheck of(Integer constant) { return newInstance(constant, 0.0, constant>0); } } public static class NumericQuorumCheck implements QuorumCheck, Serializable { private static final long serialVersionUID = -5090669237460159621L; protected final int minRequiredSize; protected final double minRequiredRatio; protected final boolean allowEmpty; protected final String name; public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) { this(minRequiredSize, minRequiredRatio, allowEmpty, null); } public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty, String name) { this.minRequiredSize = minRequiredSize; this.minRequiredRatio = minRequiredRatio; this.allowEmpty = allowEmpty; this.name = name; } @Override public boolean isQuorate(int sizeHealthy, int totalSize) { if (allowEmpty && totalSize==0) return true; if (sizeHealthy < minRequiredSize) return false; if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false; return true; } public String getName() { return name; } @Override public String toString() { return "QuorumCheck["+(name!=null?name+";":"")+"require="+minRequiredSize+","+(100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]"; } } /** See {@link QuorumChecks#newLinearRange(String,String)} */ public static class LinearRangeQuorumCheck implements QuorumCheck, Serializable { private static final long serialVersionUID = -6425548115925898645L; private static class Point { final double size, minRequiredAtSize; public Point(double size, double minRequiredAtSize) { this.size = size; this.minRequiredAtSize = minRequiredAtSize; } public static Point ofIntegerCoords(Iterable coords) { Preconditions.checkNotNull(coords==null, "coords"); Preconditions.checkArgument(Iterables.size(coords)==2, "A point must consist of two coordinates; invalid data: "+coords); Iterator ci = coords.iterator(); return new Point(ci.next(), ci.next()); } public static List listOfIntegerCoords(Iterable> points) { MutableList result = MutableList.of(); for (Iterable point: points) result.add(ofIntegerCoords(point)); return result.asUnmodifiable(); } @Override public String toString() { return "("+size+","+minRequiredAtSize+")"; } } protected final String name; protected final List points; public static LinearRangeQuorumCheck of(String name, Iterable> points) { return new LinearRangeQuorumCheck(name, Point.listOfIntegerCoords(points)); } public static LinearRangeQuorumCheck of(Iterable> points) { return new LinearRangeQuorumCheck(null, Point.listOfIntegerCoords(points)); } protected LinearRangeQuorumCheck(String name, Iterable points) { Preconditions.checkArgument(Iterables.size(points)>=2, "At least two points must be supplied for "+name+": "+points); this.name = name; this.points = MutableList.copyOf(points).asUnmodifiable(); // check valid Point last = null; for (Point p: points) { if (last!=null) { if (p.size <= last.size) throw new IllegalStateException("Points must be supplied in order of increasing totalSize (x coordinate); instead have "+last+" and "+p); } } } @Override public boolean isQuorate(int sizeHealthy, int totalSize) { Point next = points.get(0); Point prev = null; for (int i=1; itotalSize) break; } double minRequiredAtSize = (totalSize-prev.size)/(next.size-prev.size) * (next.minRequiredAtSize-prev.minRequiredAtSize) + prev.minRequiredAtSize; return (sizeHealthy > minRequiredAtSize-0.000000001); } @Override public String toString() { return "LinearRangeQuorumCheck["+(name!=null ? name+":" : "")+points+"]"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy