def _reassign_parts(self, reassign_parts, replica_plan):
        parts_available_in_tier = defaultdict(int)
        for dev in self._iter_devs():
            dev['sort_key'] = self._sort_key_for(dev)
            # Note: this represents how many partitions may be assigned to a
            # given tier (region/zone/server/disk). It does not take into
            # account how many partitions a given tier wants to shed.
            #
            # If we did not do this, we could have a zone where, at some
            # point during assignment, number-of-parts-to-gain equals
            # number-of-parts-to-shed. At that point, no further placement
            # into that zone would occur since its parts_available_in_tier
            # would be 0. This would happen any time a zone had any device
            # with partitions to shed, which is any time a device is being
            # removed, which is a pretty frequent operation.
            wanted = max(dev['parts_wanted'], 0)
            for tier in dev['tiers']:
                parts_available_in_tier[tier] += wanted

        available_devs = \
            sorted((d for d in self._iter_devs() if d['weight']),
                   key=lambda x: x['sort_key'])

        tier2devs = defaultdict(list)
        tier2sort_key = defaultdict(tuple)
        tier2dev_sort_key = defaultdict(list)
        max_tier_depth = 0
        for dev in available_devs:
            for tier in dev['tiers']:
                tier2devs[tier].append(dev)  # <-- starts out sorted!
                tier2dev_sort_key[tier].append(dev['sort_key'])
                tier2sort_key[tier] = dev['sort_key']
                if len(tier) > max_tier_depth:
                    max_tier_depth = len(tier)

        tier2children_sets = build_tier_tree(available_devs)
        tier2children = defaultdict(list)
        tier2children_sort_key = {}
        tiers_list = [()]
        depth = 1
        while depth <= max_tier_depth:
            new_tiers_list = []
            for tier in tiers_list:
                child_tiers = list(tier2children_sets[tier])
                child_tiers.sort(key=tier2sort_key.__getitem__)
                tier2children[tier] = child_tiers
                tier2children_sort_key[tier] = map(
                    tier2sort_key.__getitem__, child_tiers)
                new_tiers_list.extend(child_tiers)
            tiers_list = new_tiers_list
            depth += 1

        for part, replace_replicas in reassign_parts:
            # always update part_moves for min_part_hours
            self._last_part_moves[part] = 0
            # count up where these replicas be
            replicas_at_tier = defaultdict(int)
            for dev in self._devs_for_part(part):
                for tier in dev['tiers']:
                    replicas_at_tier[tier] += 1

            for replica in replace_replicas:
                # Find a new home for this replica
                tier = ()
                # This used to be a cute, recursive function, but it's been
                # unrolled for performance.
                depth = 1
                while depth <= max_tier_depth:
                    # Choose the roomiest tier among those that don't
                    # already have their max replicas assigned according
                    # to the replica_plan.
                    candidates = [t for t in tier2children[tier] if
                                  replicas_at_tier[t] <
                                  replica_plan[t]['max']]

                    if not candidates:
                        raise Exception('no home for %s/%s %s' % (
                            part, replica, {t: (
                                replicas_at_tier[t],
                                replica_plan[t]['max'],
                            ) for t in tier2children[tier]}))
                    tier = max(candidates, key=lambda t:
                               parts_available_in_tier[t])

                    depth += 1

                dev = tier2devs[tier][-1]
                dev['parts_wanted'] -= 1
                dev['parts'] += 1
                for tier in dev['tiers']:
                    parts_available_in_tier[tier] -= 1
                    replicas_at_tier[tier] += 1

                self._replica2part2dev[replica][part] = dev['id']
                self.logger.debug(
                    "Placed %d/%d onto dev %d", part, replica, dev['id'])

        # Just to save memory and keep from accidental reuse.
        for dev in self._iter_devs():
            del dev['sort_key']
For an existing ring data set, partitions are reassigned similarly to
dataset/ETHPy150Open openstack/swift/swift/common/ring/builder.py/RingBuilder._reassign_parts