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

oracle.kv.impl.admin.topo.TopologyDiff Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

There is a newer version: 18.3.10
Show newest version
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.admin.topo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;

import oracle.kv.impl.admin.param.Parameters;
import oracle.kv.impl.admin.param.RepNodeParams;
import oracle.kv.impl.topo.ArbNode;
import oracle.kv.impl.topo.ArbNodeId;
import oracle.kv.impl.topo.Datacenter;
import oracle.kv.impl.topo.DatacenterId;
import oracle.kv.impl.topo.DatacenterType;
import oracle.kv.impl.topo.Partition;
import oracle.kv.impl.topo.PartitionId;
import oracle.kv.impl.topo.RepGroupId;
import oracle.kv.impl.topo.RepNode;
import oracle.kv.impl.topo.RepNodeId;
import oracle.kv.impl.topo.StorageNodeId;
import oracle.kv.impl.topo.Topology;
import oracle.kv.impl.util.JsonUtils;

import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

/**
 * Compare and categorize the differences between two topologies. Used both as
 * an initial step when constructing a deployment plan and for supplying an
 * user-viewable preview of the work required to transform from one topology
 * to another.
 */

public class TopologyDiff {

    private static final String CURRENT = "current deployed topology";
    public static final String NO_CHANGE = "No differences in topologies.";

    private final Topology source;
    private final String sourceName;
    private final TopologyCandidate candidate;
    private final Parameters params;

    /*
     * Brand new shards, listed in ordinal value. The RepGroupIds are from the
     * source topology. That is, newShard.get(0) is the repGroupId for the
     * first new shard, from the source topo. When the topology is deployed,
     * the shards may not be created in precisely ordinal order, if there is
     * some parallelism, and the actual repGroupId that is allocated may be
     * different.
     */
    private final List newShards;

    /*
     * A record of all changes to all shards, such as relocated or newly
     * created RNS, or relocated partitions. At the time when the diff is
     * generated, the repGroupId of the shard in the source and candidate
     * topologies are obviously the same, but when the tasks for the topology
     * are generated by the TopoTaskGenerator, the resulting shards might have
     * ids that are different, because component id generation it controlled
     * transparently by the Topology.
     */
    private final Map changedShards;

    private final Set newRNs;

    private final Set newANs;
    private final Set removedANs;

    /* The RNs should be removed */
    private final Set removedRNs;

    /* Map of changes to zone types */
    private final Map changedZones;

    /* Map of changes to zone master affinity */
    private final Map changedAffinityZones;

    /* ===== Topology components that are now unused. ===== */

    /*
     * Note that in R2, contraction is not supported, and RNs and shards
     * should not be removed.
     */

    /* Shards that are in the source but not the candidate. */
    private final Set removedShards;

    /* ===== For reporting and validation ===== */

    /*
     * Shards that are sources for partition migration. Currently used for
     * stats, will be used for optimizing task ordering in the future.
     */
    private final Set pMigrationSources;

    /*
     * Shards that are destinations for partition migration. Currently used for
     * stats, will be used for optimizing task ordering in the future. When
     * displaying this, take care to account for the fact that the destination
     * shard id will not truly have been created yet; the RepGroupId used as
     * a destination id is a placeholder until the shard has truly been created
     * in the topology.
     */
    private final Set pMigrationDestinations;

    private int numCreatedPartitions;
    private int numPartitionMigrations;
    private int numRelocatedRNs;
    private int numRelocatedANs;


    /** Storage directory assignments created by the topology building phase. */
    private Map storageDirAssignments;

    /** RN log directory assignments created by the topology building phase. */
    private Map rnLogDirAssignments;

    /**
     * Evaluate the differences between the source and destination, or
     * candidate.
     *
     * @param source the original topology
     * @param sourceName the candidate name of the original topology. If null,
     * the default name of CURRENT will be used.
     * @param candidate the destination topology.
     */
    public TopologyDiff(Topology source, String sourceName,
                        TopologyCandidate candidate, Parameters params) {
        this(source, sourceName, candidate, params, true /*validate*/);
    }

    /**
     * Evaluate the differences between the source and destination, or
     * candidate. Optionally validate the transition.
     *
     * @param source the original topology
     * @param sourceName the candidate name of the original topology. If null,
     * the default name of CURRENT will be used.
     * @param candidate the destination topology.
     */
    public TopologyDiff(Topology source, String sourceName,
                        TopologyCandidate candidate, Parameters params,
                        boolean validate) {
        this.source = source;
        if (sourceName == null) {
            this.sourceName = CURRENT;
        } else {
            this.sourceName = sourceName;
        }
        this.candidate = candidate;
        this.params = params;

        /*
         * Sort the change set info by shard name, so the preview/display is
         * nice, and so that the generated topology transformation tasks are
         * deterministic and more intuitive to the user.
         */
        changedShards = new TreeMap<>
            (new Comparator() {
                @Override
                public
                int compare(RepGroupId r1, RepGroupId r2) {
                    return r1.getGroupId() - r2.getGroupId();
                }
            });

        numCreatedPartitions = 0;

        newShards = new ArrayList<>();
        removedShards = new HashSet<>();
        changedZones = new HashMap<>();
        changedAffinityZones = new HashMap<>();
        newRNs = new HashSet<>();
        removedRNs = new HashSet<>();
        newANs = new HashSet<>();
        removedANs = new HashSet<>();

        pMigrationSources = new HashSet<>();
        pMigrationDestinations = new HashSet<>();

        doDiff(validate);
    }

    /*
     * Methods to interrogate the diff. Should there be more, or are these
     * unnecessary?
     */
    public int getNumCreatedPartitions() {
        return numCreatedPartitions;
    }

    /**
     * Return the number of new shards to create.
     */
    public int getNumNewShards() {
        return newShards.size();
    }

    /**
     * Return the number of to-be-removed shards
     */
    public int getNumRemovedShards() {
        return removedShards.size();
    }

    /**
     * Return the number of new RNs to create.
     */
    public int getNumNewRNs() {
        return newRNs.size();
    }

    /**
     * Return the number of to-be-removed RNs
     */
    public int getNumRemovedRNs() {
        return removedRNs.size();
    }

    /**
     * Return the number of RNs to relocate.
     */
    public int getNumRelocatedRNs() {
        return numRelocatedRNs;
    }

    private int getNumNewANs() {
        return newANs.size();
    }

    private int getNumRelocatedANs() {
        return numRelocatedANs;
    }

    private int getNumRemovedANs() {
        return removedANs.size();
    }


    /**
     * Returns true if the specified zone type is changed.
     */
    public boolean typeChanged(DatacenterId zoneId) {
        return changedZones.containsKey(zoneId);
    }

    /**
     * Returns true if the specified zone master affinity is changed.
     */
    public boolean affinityChanged(DatacenterId zoneId) {
        return changedAffinityZones.containsKey(zoneId);
    }

    /**
     * Return a listing of the work to be done. Format is
     * Summary:
     * Change N zone types
     * Create N shards
     * Create N RNs
     * Migrate N partitions
     *
     * change ZN to TYPE
     * shard X
     *  Y new RNs: rg7-rn1 rg7-rn2 rg7-rn3
     *  Z partition migrations
     *
     *  if verbose, a listing of all the partitions to migrated follows, sorted
     *  by source rep group, and then by partition id.
     */
    public String display(boolean verbose) {
        final StringBuilder sb = new StringBuilder();

        /* Summary information. */

        if (!changedZones.isEmpty()) {
            sb.append("Change ").append(changedZones.size()).
                append(" zone type").append(plural(changedZones.size())).
                append("\n");
        }

        if (!changedAffinityZones.isEmpty()) {
            sb.append("Change ").append(changedAffinityZones.size()).
                append(" zone master ").
                append(changedAffinityZones.size() == 0 ?
                       "affinity" :
                       "affinities").
                append("\n");
        }

        int nShards = getNumNewShards();
        if (nShards > 0) {
            sb.append("Create ").append(nShards).
                append(" shard").append(plural(nShards)).append("\n");
        }

        int nNewRNs = getNumNewRNs();
        if (nNewRNs > 0) {
            sb.append("Create ").append(nNewRNs).append(" RN").
                append(plural(nNewRNs)).append("\n");
        }

        /* Show the number of to-be-removed RNs */
        int nRemovedRNs = getNumRemovedRNs();
        if (nRemovedRNs > 0) {
            sb.append("Remove ").append(nRemovedRNs).append(" RN").
                append(plural(nRemovedRNs)).append("\n");
        }

        /* Show the number of to-be-removed shards */
        int nRemovedShards = getNumRemovedShards();
        if (nRemovedShards > 0) {
            sb.append("Remove ").append(nRemovedShards).append(" shard").
                append(plural(nRemovedShards)).append("\n");
        }

        int moved = getNumRelocatedRNs();
        if (moved > 0) {
            sb.append("Relocate ").append(moved).append(" RN").
                append(plural(moved)).append("\n");
        }

        if (numCreatedPartitions != 0) {
            sb.append("Create ").append(numCreatedPartitions).
                append(" partition").append(plural(numCreatedPartitions)).
                append("\n");
        }

        if (numPartitionMigrations != 0) {
            sb.append("Migrate ").append(numPartitionMigrations).
                append(" partition").append(plural(numPartitionMigrations)).
                append("\n");
        }

        int nNewANs = getNumNewANs();
        if (nNewANs > 0) {
            sb.append("Create ").append(nNewANs).append(" AN").
                append(plural(nNewANs)).append("\n");
        }

        moved = getNumRelocatedANs();
        if (moved > 0) {
            sb.append("Relocate ").append(moved).append(" AN").
                append(plural(moved)).append("\n");
        }

        moved = getNumRemovedANs();
        if (moved > 0) {
            sb.append("Remove ").append(moved).append(" AN").
                append(plural(moved)).append("\n");
        }

        /* If some summary information has been generated, add a newline */
        if (sb.length() > 0) {
            sb.append("\n");
        }

        for (Entry e : changedZones.entrySet()) {
            sb.append("change ").append(e.getKey()).
                append(" to ").append(e.getValue()).append("\n");
        }

        for (Entry e : changedAffinityZones.entrySet()) {
            sb.append("change ").append(e.getKey()).
                append(" master affinity to ").
                append(e.getValue()).append("\n");
        }
        final String indent = "  ";

        for (Map.Entry s : changedShards.entrySet()) {
            sb.append("shard ").append(s.getKey());
            if (removedShards.contains(s.getKey())) {
                sb.append(" (to be removed)");
            }
            sb.append("\n");
            ShardChange c = s.getValue();
            int newCreates = c.newlyCreatedRNs.size();
            if (newCreates > 0) {
                sb.append(indent);
                sb.append(newCreates).append(" new RN").
                    append(plural(newCreates)).append(": ");

                for (RepNodeId rnId : c.newlyCreatedRNs) {
                    sb.append(rnId).append(" ");
                }
                sb.append("\n");
            }

            /* Display to-be-removed RNs against a shard */
            int numRemovedRNsByShard = c.removedRNs.size();
            if (numRemovedRNsByShard > 0) {
                sb.append(indent);
                sb.append(numRemovedRNsByShard).append(" to be removed RN")
                        .append(plural(numRemovedRNsByShard)).append(": ");

                for (RepNodeId rnId : c.removedRNs) {
                    sb.append(rnId).append(" ");
                }
                sb.append("\n");
            }

            if (!c.getRelocatedRNs().isEmpty()) {
                for (RelocatedRN rel : c.getRelocatedRNs()) {
                    sb.append(indent).append(rel).append("\n");
                }
            }

            newCreates = c.newlyCreatedANs.size();
            if (newCreates > 0) {
                sb.append(indent);
                sb.append(newCreates).append(" new AN").
                    append(plural(newCreates)).append(": ");

                for (ArbNodeId arbId : c.newlyCreatedANs) {
                    sb.append(arbId).append(" ");
                }
                sb.append("\n");
            }

            if (!c.getRelocatedANs().isEmpty()) {
                for (RelocatedAN rel : c.getRelocatedANs()) {
                    sb.append(indent).append(rel).append("\n");
                }
            }

            final int nRemovedANs = c.removedANs.size();
            if (nRemovedANs > 0) {
                sb.append(indent);
                sb.append(nRemovedANs).append(" remove AN").
                    append(plural(nRemovedANs)).append(": ");

                for (ArbNodeId arbId : c.removedANs) {
                    sb.append(arbId).append(" ");
                }
                sb.append("\n");
            }

            if (c.newPartitionCount != 0) {
                sb.append(indent).append(c.newPartitionCount).
                    append(" new partition").
                    append(plural(c.newPartitionCount)).append("\n");
            }

            int nMigrates = c.migrations.size();
            if (nMigrates > 0) {
                sb.append(indent).append(nMigrates).
                    append(" partition migration").append(plural(nMigrates)).
                    append("\n");
                if (verbose) {
                    for (RelocatedPartition p :
                             sortBySrcAndPart(c.migrations)) {
                        sb.append(indent).append(p).append("\n");
                    }
                }
            }
        }

        if (sb.length() == 0) {
            return NO_CHANGE;
        }
        return "Topology transformation from " + sourceName + " to " +
            candidate.getName() + ":\n" + sb.toString();
    }

    public ObjectNode displayJson(boolean verbose) {

        final ObjectNode top = JsonUtils.createObjectNode();

        if (!changedZones.isEmpty()) {
            top.put("changeZoneTypeTotal", changedZones.size());
        }

        if (!changedAffinityZones.isEmpty()) {
            top.put("changeZoneAffinityTotal", changedAffinityZones.size());
        }

        int nShards = getNumNewShards();
        if (nShards > 0) {
            top.put("createShardTotal", nShards);
        }

        int nNewRNs = getNumNewRNs();
        if (nNewRNs > 0) {
            top.put("createRnTotal", nNewRNs);
        }

        /* Show the number of to-be-removed RNs */
        int nRemovedRNs = getNumRemovedRNs();
        if (nRemovedRNs > 0) {
            top.put("removeRnTotal", nRemovedRNs);
        }

        /* Show the number of to-be-removed shards */
        int nRemovedShards = getNumRemovedShards();
        if (nRemovedShards > 0) {
            top.put("removeShardTotal", nRemovedShards);
        }

        int moved = getNumRelocatedRNs();
        if (moved > 0) {
            top.put("relocateRnTotal", moved);
        }

        if (numCreatedPartitions != 0) {
            top.put("createPartitionTotal", numCreatedPartitions);
        }

        if (numPartitionMigrations != 0) {
            top.put("migratePartitionTotal", numPartitionMigrations);
        }

        int nNewANs = getNumNewANs();
        if (nNewANs > 0) {
            top.put("createAnTotal", nNewANs);
        }

        moved = getNumRelocatedANs();
        if (moved > 0) {
            top.put("relocateAnTotal", moved);
        }

        moved = getNumRemovedANs();
        if (moved > 0) {
            top.put("removeAnTotal", moved);
        }

        final ArrayNode changeZoneArray = top.putArray("changeZones");
        for (Entry e : changedZones.entrySet()) {
            final ObjectNode on = JsonUtils.createObjectNode();
            on.put("change", e.getKey().toString());
            on.put("to", e.getValue().toString());
            changeZoneArray.add(on);
        }

        final ArrayNode changeZoneAffinityArray =
            top.putArray("changeAffinityZones");
        for (Entry e :
                 changedAffinityZones.entrySet()) {
            final ObjectNode on = JsonUtils.createObjectNode();
            on.put("change", e.getKey().toString());
            on.put("to", e.getValue().toString());
            changeZoneAffinityArray.add(on);
        }

        final ArrayNode shardArray = top.putArray("changeShards");
        for (Map.Entry s : changedShards.entrySet()) {
            final ObjectNode on = JsonUtils.createObjectNode();
            on.put("shard", s.getKey().toString());
            on.put("remove", false);
            if (removedShards.contains(s.getKey())) {
                on.put("remove", true);
            }
            final ArrayNode createRnArray = on.putArray("createRns");
            ShardChange c = s.getValue();
            int newCreates = c.newlyCreatedRNs.size();
            if (newCreates > 0) {
                for (RepNodeId rnId : c.newlyCreatedRNs) {
                    createRnArray.add(rnId.toString());
                }
            }

            /* Display to-be-removed RNs against a shard */
            final ArrayNode removeRnArray = on.putArray("removeRns");
            int numRemovedRNsByShard = c.removedRNs.size();
            if (numRemovedRNsByShard > 0) {
                for (RepNodeId rnId : c.removedRNs) {
                    removeRnArray.add(rnId.toString());
                }
            }

            final ArrayNode relocateRnArray = on.putArray("relocateRns");
            if (!c.getRelocatedRNs().isEmpty()) {
                for (RelocatedRN rel : c.getRelocatedRNs()) {
                    relocateRnArray.add(rel.toString());
                }
            }

            final ArrayNode createAnArray = on.putArray("createAns");
            newCreates = c.newlyCreatedANs.size();
            if (newCreates > 0) {
                for (ArbNodeId arbId : c.newlyCreatedANs) {
                    createAnArray.add(arbId.toString());
                }
            }

            final ArrayNode relocateAnArray = on.putArray("relocateAns");
            if (!c.getRelocatedANs().isEmpty()) {
                for (RelocatedAN rel : c.getRelocatedANs()) {
                    relocateAnArray.add(rel.toString());
                }
            }

            final ArrayNode removeAnArray = on.putArray("removeAns");
            final int nRemovedANs = c.removedANs.size();
            if (nRemovedANs > 0) {
                for (ArbNodeId arbId : c.removedANs) {
                    removeAnArray.add(arbId.toString());
                }
            }

            if (c.newPartitionCount != 0) {
                on.put("newPartitionCount", c.newPartitionCount);
            }

            int nMigrates = c.migrations.size();
            if (nMigrates > 0) {
                on.put("migratePartitionCount", nMigrates);
                if (verbose) {
                    final ArrayNode migratePartitionArray =
                        on.putArray("migratePartitions");
                    for (RelocatedPartition p :
                             sortBySrcAndPart(c.migrations)) {
                        migratePartitionArray.add(p.toString());
                    }
                }
            }
            shardArray.add(on);
        }

        return top;
    }

    /**
     * Return an "s" if the value is > 1, so the plurality is right in the
     * display.
     */
    private String plural(int val) {
        return (val==1) ? " " : "s ";
    }

    /**
     * Sort the partition migrations first by source shard, and then by
     * partition id, for display.
     */
    private List sortBySrcAndPart
        (List migrations) {

        List relPart = new ArrayList<>(migrations);
        Collections.sort
            (relPart,
             new Comparator() {
                @Override
                public int compare(RelocatedPartition p1,
                                   RelocatedPartition p2) {
                    int p1Src = p1.getSourceShard().getGroupId();
                    int p2Src = p2.getSourceShard().getGroupId();

                    int shardDiff = p1Src - p2Src;
                    if (shardDiff != 0) {
                        return shardDiff;
                    }
                    return p1.getPartitionId().getPartitionId() -
                        p2.getPartitionId().getPartitionId();
                }});
        return relPart;
    }

    /**
     * Return information about shards that have changed. This includes shards
     * that are newly created.
     */
    public Map getChangedShards() {
        return changedShards;
    }

    /**
     * Return the different in the number of shards between the two topologies.
     */
    public static int shardDelta(Topology topoA, Topology topoB) {
        return topoA.getRepGroupMap().size() - topoB.getRepGroupMap().size();
    }

    /**
     * The actual work of doing the diff.
     * @throws InvalidTopologyException
     */
    private void doDiff(boolean validate) {

        /*
         * Check that the source and target topologies obey all rules, and
         * that source can be legitimately transformed into the target.
         * Validation may be turned off if TopologyDiff is being used solely
         * to display potential transitions.
         */
        if (validate) {
            Rules.validateTransition(source, candidate, params);
        }

        findChangedZones();

        /*
         * The topology was validated earlier to ensure that R2 assumptions are
         * correct.
         *
         * Find the set of brand new RNs that are only in the target topology,
         * and need to be created.
         */
        Topology target = candidate.getTopology();
        Set originalRNs = source.getRepNodeIds();
        newRNs.addAll(target.getRepNodeIds());
        newRNs.removeAll(originalRNs);

        /* Find to-be-removed RNs */
        removedRNs.addAll(originalRNs);
        removedRNs.removeAll(target.getRepNodeIds());
        /* The new created RNs won't be removed, so remove them from the set */
        removedRNs.removeAll(newRNs);

        /* Group the brand new RNs by shard. */
        identifyNewRNs();

        /* Group the to be deleted RNs by shard. */
        identifyRemovedRNs();

        /*
         * Identify all the RNs that have been relocated, grouping them
         * by shard.
         */
        identifyRelocatedRNs(originalRNs);

        Set originalANs = source.getArbNodeIds();
        Set targetANs = target.getArbNodeIds();
        newANs.addAll(targetANs);
        newANs.removeAll(originalANs);

        removedANs.addAll(originalANs);
        removedANs.removeAll(targetANs);

        identifyANsToShards();

        identifyRelocatedANs(originalANs);


        /*
         * Find all the shards that did not exist before..
         */
        findNewShards();

        /* Find partitions that need to be moved or created. */
        findPartitionChanges();

        storageDirAssignments = candidate.getStorageDirAssignments(params);

        rnLogDirAssignments = candidate.getRNLogDirAssignments(params);
    }

    /*
     * Finds zones that have changed types. The zone ID and new type are added
     * to the changeZones map.
     */
    private void findChangedZones() {
        for (Datacenter dc :
                          candidate.getTopology().getDatacenterMap().getAll()) {
            final Datacenter oldDc = source.get(dc.getResourceId());
            if (oldDc == null) {
                continue;
            }
            if (!oldDc.getDatacenterType().equals(dc.getDatacenterType())) {
                changedZones.put(dc.getResourceId(), dc.getDatacenterType());
            }
            if (oldDc.getMasterAffinity() != dc.getMasterAffinity()) {
                changedAffinityZones.put(dc.getResourceId(),
                                         dc.getMasterAffinity());
            }
        }
    }

    /**
     * Find shards that have new RNs.
     */
    private void identifyNewRNs() {

        /* Identify all brand new RNs, grouping them by shard. */
        for (RepNodeId rnId : newRNs) {
            ShardChange change =
                getShardChange(new RepGroupId(rnId.getGroupId()));
            change.addNewRN(rnId);
        }
    }

    /**
     * Add information about which shards that have RNs to be removed.
     */
    private void identifyRemovedRNs() {
        for (RepNodeId rnId : removedRNs) {
            ShardChange change =
                getShardChange(new RepGroupId(rnId.getGroupId()));
            change.addRemovedRN(rnId);
        }
    }

    /**
     * Add information about which shards have relocated RNs
     */
    private void identifyRelocatedRNs(Set originalRNs) {
        Topology target = candidate.getTopology();
        for (RepNodeId existingRNId : originalRNs) {
            StorageNodeId sourceSNId =
                source.get(existingRNId).getStorageNodeId();
            RepNode targetRN = target.get(existingRNId);

            /*
             * Removed RN does not existing in target topology, it is not the
             * relocated RN
             */
            if (targetRN == null) {
                continue;
            }
            StorageNodeId targetSNId = targetRN.getStorageNodeId();

            if (!sourceSNId.equals(targetSNId)) {
                final DatacenterId sourceZoneId =
                        source.getDatacenter(sourceSNId).getResourceId();
                final DatacenterId targetZoneId =
                        target.getDatacenter(targetSNId).getResourceId();
                if (!sourceZoneId.equals(targetZoneId)) {
                    throw new UnsupportedOperationException(
                            "Cannot move " + existingRNId + " from zone " +
                            sourceZoneId + " to " + targetZoneId);
                }
                String sourceStorageDirPath = null;
                String sourceRNLogDirPath = null;
                RepNodeParams rnp = params.get(existingRNId);
                if (rnp != null) {
                    sourceStorageDirPath = rnp.getStorageDirectoryPath();
                    sourceRNLogDirPath = rnp.getLogDirectoryPath();
                }
                final StorageDirectory targetStorageDir =
                    candidate.getStorageDir(existingRNId, params);
                final LogDirectory targetRNLogDir =
                    candidate.getRNLogDir(existingRNId, params);
                RelocatedRN rel = new RelocatedRN(existingRNId,
                                                  sourceSNId,
                                                  sourceStorageDirPath,
                                                  sourceRNLogDirPath,
                                                  targetSNId,
                                                  targetStorageDir,
                                                  targetRNLogDir,
                                                  params);

                ShardChange change =
                    getShardChange(new RepGroupId(existingRNId.getGroupId()));
                change.addRelocatedRN(rel);
                numRelocatedRNs++;
            }
        }
    }

    /**
     * Add information about which shards have relocated Arbiters
     */
    private void identifyRelocatedANs(Set originalARBs) {
        Topology target = candidate.getTopology();
        for (ArbNodeId existingARBId : originalARBs) {
            StorageNodeId sourceSNId =
                source.get(existingARBId).getStorageNodeId();
            ArbNode targetAN = target.get(existingARBId);
            if (targetAN == null) {
                /* AN is not in the target topo */
                continue;
            }
            StorageNodeId targetSNId = targetAN.getStorageNodeId();

            if (!sourceSNId.equals(targetSNId)) {
                RelocatedAN rel = new RelocatedAN(existingARBId,
                                                  sourceSNId,
                                                  targetSNId);

                ShardChange change =
                    getShardChange(new RepGroupId(existingARBId.getGroupId()));
                change.addRelocatedANs(rel);
                numRelocatedANs++;
            }
        }
    }

    /**
     * Find shards and add/remove Arbiters
     */
    private void identifyANsToShards() {

        /* Identify all brand new ARBs, grouping them by shard. */
        for (ArbNodeId arbId : newANs) {
            ShardChange change =
                getShardChange(new RepGroupId(arbId.getGroupId()));
            change.addNewAN(arbId);
        }

        /* Identify all removed ANs, grouping them by shard. */
        for (ArbNodeId arbId : removedANs) {
            ShardChange change =
                getShardChange(new RepGroupId(arbId.getGroupId()));
            change.addRemovedAN(arbId);
        }
    }

    /**
     *  Find all the shards which did not exist before.
     */
    private void findNewShards() {
        Topology target = candidate.getTopology();
        Set originalShards = source.getRepGroupIds();
        Set candidateShards = target.getRepGroupIds();

        Set brandNew = new HashSet<>(candidateShards);
        brandNew.removeAll(originalShards);
        newShards.addAll(brandNew);

        /*
         * Sort the new shard list so it's ordered by RepGroupId. This will
         * make the actual initial creation of partitions in the AddPartition
         * task match the candidate topology. New partition creation is
         * driven by a count of partitions on the ordinal shard rather than
         * an actual match of partition ids and shard ids, because the
         * shard id and partition id are generated internally by the topology,
         * and can't be specified by externally when constructing those items.
         */
        Collections.sort(newShards);

        removedShards.addAll(originalShards);
        removedShards.removeAll(candidateShards);
    }

    /**
     * Assumes that the newShards list has been populated before this is
     * called, as it assigns partitions to shards..
     */
    private void findPartitionChanges() {
        Topology target = candidate.getTopology();

        if (source.getPartitionMap().isEmpty()) {
            /* Brand new store, assign partitions to new shards. */
            for (Partition p: target.getPartitionMap().getAll()) {
                ShardChange change = getShardChange(p.getRepGroupId());
                change.incNewPartionCount();
                numCreatedPartitions++;
            }
            return;
        }

        /*
         * Before looking at partition assignments, set aside RNs that already
         * have the right number of partitions. Assume that all partitions
         * are the same, and that we don't want to move partitions on or off
         * a RN if it already houses the right number. This guards against
         * inadvertent, unnecessary movement of partitions. In future releases,
         * we may give partitions different weight, and there may be a reason
         * to move a partitions even though the number of partitions on the
         * source or destination are unchanged.
         */

        /* Count the number of partitions per shard in the old topo */
        Map sourceCount = new HashMap<>();
        for (RepGroupId rgId: source.getRepGroupIds()) {
            sourceCount.put(rgId, new AtomicInteger(0));
        }
        for (Partition p: source.getPartitionMap().getAll()) {
            sourceCount.get(p.getRepGroupId()).incrementAndGet();
        }

        /* Count the number of partitions per shard in the new topo */
        Map targetCount = new HashMap<>();
        for (RepGroupId rgId: target.getRepGroupIds()) {
            targetCount.put(rgId, new AtomicInteger(0));
        }
        for (Partition p: target.getPartitionMap().getAll()) {
            targetCount.get(p.getRepGroupId()).incrementAndGet();
        }

        Set changed = new HashSet<>();
        for (RepGroupId rgId: target.getRepGroupIds()) {
            if (sourceCount.get(rgId) == null) {
                changed.add(rgId);
            } else if (sourceCount.get(rgId).get() !=
                       targetCount.get(rgId).get()) {
                changed.add(rgId);
            }
        }

        /*
         * The to-be-removed shard existing in source topology and not existing
         * in target topology
         */
        for (RepGroupId rgId : source.getRepGroupIds()) {
            if (targetCount.get(rgId) == null) {
                changed.add(rgId);
            }
        }

        for (Partition p: target.getPartitionMap().getAll()) {
            PartitionId pId = p.getResourceId();
            RepGroupId oldShard = source.get(pId).getRepGroupId();
            RepGroupId newShard = target.get(pId).getRepGroupId();

            /*
             * If the shard assignments are different AND the old and new shard
             * changed the numbers of partitions they house, do a migration.
             */
            if ((!oldShard.equals(newShard)) &&
                (changed.contains(oldShard) && changed.contains(newShard))){

                ShardChange change = getShardChange(newShard);
                change.add(new RelocatedPartition(pId, oldShard, newShard));
                pMigrationSources.add(oldShard);
                pMigrationDestinations.add(newShard);
                numPartitionMigrations++;
            }
        }
    }

    /**
     * Get the appropriate ShardChange instance, creating one of it does not
     * already exist.
     */
    public ShardChange getShardChange(RepGroupId id) {
        ShardChange change = changedShards.get(id);
        if (change == null) {
            change = new ShardChange();
            changedShards.put(id, change);
        }
        return change;
    }

    /**
     * @return a list of newly created shards.
     */
    public List getNewShards() {
        return newShards;
    }

    /**
     * @return a list of to be removed shards.
     */
    public Set getRemovedShards() {
        return removedShards;
    }

    /**
     * Returns the storage directory assigned to the specified RN.
     */
    public StorageDirectory getStorageDir(RepNodeId rnId) {
        return storageDirAssignments.get(rnId);
    }

    /**
     * Returns the RN log directory assigned to the specified RN.
     */
    public LogDirectory getRNLogDir(RepNodeId rnId) {
        return rnLogDirAssignments.get(rnId);
    }

    /**
     * Records the changes that have to be made to a shard; which is to add or
     * move RNs, and add partitions.
     */
    public static class ShardChange {

        /* RNs that need to be created */
        private final Set newlyCreatedRNs;

        /* RNs that need to be removed */
        private final Set removedRNs;

        /* RNs that are to be moved from one SN to another. */
        private final Set relocatedRNs;

        /* ANs that need to be created */
        private final Set newlyCreatedANs;

        /* ANs that are to be moved from one SN to another. */
        private final Set relocatedANs;

        private final Set removedANs;


        /*
         * The number of partitions that should be created on this shard.
         * Applies only to brand new shards. Partitions that are migrated
         * are recorded as a PartitionMigration.
         */
        private int newPartitionCount;

        /*
         * Partitions that are to be migrated to this shard.
         */
        private final List migrations;

        ShardChange() {

            /*
             * Keep the the relocated and newly created RNs sorted by node id,
             * so that the display of the diff is nicer. Also makes task
             * construction and order more deterministic and intuitive to the
             * user.
             */
            relocatedRNs = new TreeSet<>
            (new Comparator() {
                @Override
                public int compare(RelocatedRN o1, RelocatedRN o2) {
                    return o1.getRnId().getNodeNum() -
                        o2.getRnId().getNodeNum();
                }

            });
            newlyCreatedRNs = new TreeSet<>
            (new Comparator() {
                @Override
                public int compare(RepNodeId o1, RepNodeId o2) {
                    return o1.getNodeNum() - o2.getNodeNum();
                }

            });
            removedRNs = new TreeSet<>
            (new Comparator() {
                @Override
                public int compare(RepNodeId o1, RepNodeId o2) {
                    return o1.getNodeNum() - o2.getNodeNum();
                }

            });

            relocatedANs = new TreeSet<>
            (new Comparator() {
                @Override
                public int compare(RelocatedAN o1, RelocatedAN o2) {
                    return o1.getArbId().getNodeNum() -
                        o2.getArbId().getNodeNum();
                }

            });
            newlyCreatedANs = new TreeSet<>
            (new Comparator() {
                @Override
                public int compare(ArbNodeId o1, ArbNodeId o2) {
                    return o1.getNodeNum() - o2.getNodeNum();
                }

            });
            removedANs = new TreeSet<>
            (new Comparator() {
                @Override
                public int compare(ArbNodeId o1, ArbNodeId o2) {
                    return o1.getNodeNum() - o2.getNodeNum();
                }

            });
            migrations = new ArrayList<>();
        }

        public List getMigrations() {
            return migrations;
        }

        public void add(RelocatedPartition relocatedPartition) {
            migrations.add(relocatedPartition);

        }

        void incNewPartionCount() {
            newPartitionCount++;
        }

        public int getNumNewPartitions() {
            return newPartitionCount;
        }

        public Set getNewRNs() {
            return newlyCreatedRNs;
        }

        void addNewRN(RepNodeId rnId) {
            newlyCreatedRNs.add(rnId);
        }

        void addRemovedRN(RepNodeId rnId) {
            removedRNs.add(rnId);
        }

        public Set getRelocatedRNs() {
            return relocatedRNs;
        }

        void addRelocatedRN(RelocatedRN rel) {
            relocatedRNs.add(rel);
        }

        public Set getNewANs() {
            return newlyCreatedANs;
        }

        void addNewAN(ArbNodeId arbId) {
            newlyCreatedANs.add(arbId);
        }

        public Set getRemovedANs() {
            return removedANs;
        }

        void addRemovedAN(ArbNodeId arbId) {
            removedANs.add(arbId);
        }

        public Set getRelocatedANs() {
            return relocatedANs;
        }

        void addRelocatedANs(RelocatedAN rel) {
            relocatedANs.add(rel);
        }

        /**
         * Make a string that contains the set of SNs that house this shard,
         * purely for debugging/descriptive purposes.
         */
        public String getSNSetDescription(Topology topo) {
            StringBuilder sb = new StringBuilder();
            for (RepNodeId rnId : newlyCreatedRNs) {
                StorageNodeId snId = topo.get(rnId).getStorageNodeId();
                sb.append(topo.get(snId).toString());
                sb.append(" ");
            }
            return sb.toString();
        }
    }

    /**
     * Records info for RNs that have moved to a different SN.
     */
    public static class RelocatedRN {
        private final RepNodeId rnId;
        private final StorageNodeId oldSNId;
        private final StorageNodeId newSNId;
        private final String oldStorageDirPath;
        private final StorageDirectory newStorageDir;
        private final String oldRNLogDirPath;
        private final LogDirectory newRNLogDir;
        private final String oldRootDir;
        private final String newRootDir;

        private RelocatedRN(RepNodeId rnId,
                            StorageNodeId oldSNId,
                            String oldStorageDirPath,
                            String oldRNLogDirPath,
                            StorageNodeId newSNId,
                            StorageDirectory newStorageDir,
                            LogDirectory newRNLogDir,
                            Parameters params) {
            this.rnId = rnId;
            this.oldSNId = oldSNId;
            this.oldStorageDirPath = oldStorageDirPath;
            this.oldRNLogDirPath = oldRNLogDirPath;
            this.oldRootDir = params.get(oldSNId).getRootDirPath();
            this.newSNId = newSNId;
            this.newStorageDir = newStorageDir;
            this.newRNLogDir = newRNLogDir;
            this.newRootDir = params.get(newSNId).getRootDirPath();
        }

        public RepNodeId getRnId() {
            return rnId;
        }

        public StorageNodeId getNewSNId() {
            return newSNId;
        }

        public StorageNodeId getOldSNId() {
            return oldSNId;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("Relocate ").append(rnId).append(" from ");

            sb.append(oldSNId).append(" storagedir=");
            if (oldStorageDirPath == null) {
                sb.append(oldRootDir);
            } else {
                sb.append(oldStorageDirPath);
            }
            sb.append(" rnlogdir=");
            if (oldRNLogDirPath == null) {
                sb.append(oldRootDir);
            } else {
                sb.append(oldRNLogDirPath);
            }

            sb.append(" to ");

            sb.append(newSNId).append(" storagedir=");
            if (newStorageDir.getPath() == null) {
                sb.append(newRootDir);
            } else {
                sb.append(newStorageDir.getPath());
            }
            sb.append(" rnlogdir=");
            if (newRNLogDir.getPath() == null) {
                sb.append(newRootDir);
            } else {
                sb.append(newRNLogDir.getPath());
            }
            return sb.toString();
        }
    }

    /**
     * Records info for ANs that have moved to a different SN.
     */
    public static class RelocatedAN {
        private final ArbNodeId arbId;
        private final StorageNodeId oldSNId;
        private final StorageNodeId newSNId;

        RelocatedAN(ArbNodeId arbId,
                    StorageNodeId oldSNId,
                    StorageNodeId newSNId) {
            this.arbId = arbId;
            this.oldSNId = oldSNId;
            this.newSNId = newSNId;
        }

        public ArbNodeId getArbId() {
            return arbId;
        }

        public StorageNodeId getNewSNId() {
            return newSNId;
        }

        public StorageNodeId getOldSNId() {
            return oldSNId;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("Relocate ").append(arbId).append(" from ");
            sb.append(oldSNId).append(" ");
            sb.append(" to ");
            sb.append(newSNId).append(" ");
            return sb.toString();
        }
    }

    /**
     * Records info for partitions that have moved to a new shard.
     */
    public static class RelocatedPartition {

        private final PartitionId partitionId;
        private final RepGroupId sourceShard;
        private final RepGroupId targetShard;

        public RelocatedPartition(PartitionId partitionId,
                                  RepGroupId sourceShard,
                                  RepGroupId targetShard) {
            this.partitionId = partitionId;
            this.sourceShard = sourceShard;
            this.targetShard = targetShard;
        }

        public PartitionId getPartitionId() {
            return partitionId;
        }

        public RepGroupId getSourceShard() {
            return sourceShard;
        }

        @Override
        public String toString() {
            return "Migrate " + partitionId + " from " + sourceShard + " to " +
                   targetShard;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy