'Rebalance the ring.
This is the main work function of the builder, as it will assign and
reassign partitions to devices in the ring based on weights, distinct
zones, recent reassignments, etc.
The process doesn\'t always perfectly assign partitions (that\'d take a
lot more analysis and therefore a lot more time -- I had code that did
that before). Because of this, it keeps rebalancing until the device
skew (number of partitions a device wants compared to what it has) gets
below 1% or doesn\'t change by more than 1% (only happens with a ring
that can\'t be balanced no matter what).
:param seed: a value for the random seed (optional)
:returns: (number_of_partitions_altered, resulting_balance,
number_of_removed_devices)'

def rebalance(self, seed=None):

    num_devices = 0
    for dev in self._iter_devs():
        dev['tiers'] = tiers_for_dev(dev)
        if (dev['weight'] > 0):
            num_devices += 1
    if (num_devices < self.replicas):
        raise exceptions.RingValidationError(('Replica    count    of    %(replicas)s    requires    more    than    %(num_devices)s    devices' % {'replicas': self.replicas, 'num_devices': num_devices}))
    if (seed is not None):
        random.seed(seed)
    self._ring = None
    old_replica2part2dev = copy.deepcopy(self._replica2part2dev)
    if (not self.ever_rebalanced):
        self.logger.debug('New    builder;    performing    initial    balance')
        self._last_part_moves = array('B', itertools.repeat(0, self.parts))
    self._update_last_part_moves()
    replica_plan = self._build_replica_plan()
    self._set_parts_wanted(replica_plan)
    assign_parts = defaultdict(list)
    self._adjust_replica2part2dev_size(assign_parts)
    removed_devs = self._gather_parts_from_failed_devices(assign_parts)
    self._gather_parts_for_dispersion(assign_parts, replica_plan)
    for gather_count in range(MAX_BALANCE_GATHER_COUNT):
        self._gather_parts_for_balance(assign_parts, replica_plan)
        if (not assign_parts):
            finish_status = 'Unable    to    finish'
            break
        assign_parts_list = list(assign_parts.items())
        random.shuffle(assign_parts_list)
        assign_parts = defaultdict(list)
        num_part_replicas = sum((len(r) for (p, r) in assign_parts_list))
        self.logger.debug('Gathered    %d    parts', num_part_replicas)
        self._reassign_parts(assign_parts_list, replica_plan)
        self.logger.debug('Assigned    %d    parts', num_part_replicas)
        if (not sum(((d['parts_wanted'] < 0) for d in self._iter_devs()))):
            finish_status = 'Finished'
            break
    else:
        finish_status = 'Unable    to    finish'
    self.logger.debug('%(status)s    rebalance    plan    after    %(count)s    attempts', {'status': finish_status, 'count': (gather_count + 1)})
    self.devs_changed = False
    self.version += 1
    changed_parts = self._build_dispersion_graph(old_replica2part2dev)
    for dev in self._iter_devs():
        dev.pop('tiers', None)
    return (changed_parts, self.get_balance(), removed_devs)
