From 3c09d9bfd50149494bb8b561ed289f944af34fd0 Mon Sep 17 00:00:00 2001 From: Matthew Oliver Date: Fri, 10 Feb 2017 17:19:30 +1100 Subject: [PATCH] Rework swift_rings.py to use the RingBuilder class The old version of this scipt used to interface to the ringbuilder cli interface. This meant we did some crazy threading. That was complicated. This patch changes that to use the RingBuilder and RingData classes, which makes things much simpler, and we can remove all the threading stuff. Change-Id: I94004db3b2b772644d89e20c1201d7f403f3eb86 --- templates/swift_rings.py.j2 | 208 +++++++++++++++--------------------- 1 file changed, 84 insertions(+), 124 deletions(-) diff --git a/templates/swift_rings.py.j2 b/templates/swift_rings.py.j2 index 04d1f126..7f190509 100644 --- a/templates/swift_rings.py.j2 +++ b/templates/swift_rings.py.j2 @@ -15,14 +15,16 @@ from __future__ import print_function from optparse import OptionParser -from os.path import exists +from os.path import exists, dirname, join, basename -from swift.cli.ringbuilder import main as rb_main +from swift.common.ring import RingBuilder +from swift.common.ring.utils import parse_builder_ring_filename_args import json -import pickle import sys -import threading +import os +import time +from datetime import timedelta USAGE = "usage: %prog -f -r " @@ -36,54 +38,8 @@ class RingValidationError(Exception): pass -class ThreadWithErr(threading.Thread): - def run(self): - try: - threading.Thread.run(self) - except BaseException as err: - self.err = err - else: - self.err = None - - -def create_buildfile(build_file, part_power, repl, min_part_hours, - update=False, data=None, validate=False): - if update: - # build file exists, so lets just update the existing build file - if not data: - data = get_build_file_data(build_file) - if data is None: - data = {} - - if repl != data.get('replicas') and not validate: - run_and_wait(rb_main, ["swift-ring-builder", build_file, - "set_replicas", repl]) - if min_part_hours != data.get('min_part_hours') and not validate: - run_and_wait(rb_main, ["swift-ring-builder", build_file, - "set_min_part_hours", min_part_hours]) - if part_power != data.get('part_power'): - raise RingValidationError('Part power cannot be changed! ' - 'you must rebuild the ring if you need ' - 'to change it.\nRing part power: %s ' - 'Inventory part power: %s' - % (data.get('part_power'), part_power)) - - elif not validate: - run_and_wait(rb_main, ["swift-ring-builder", build_file, "create", - part_power, repl, min_part_hours]) - - -def change_host_weight(build_file, host_search_str, weight): - run_and_wait(rb_main, ["swift-ring-builder", build_file, "set_weight", - host_search_str, str(weight)]) - - -def remove_host_from_ring(build_file, host): - run_and_wait(rb_main, ["swift-ring-builder", build_file, "remove", - host]) - - -def update_host_in_ring(build_file, new_host, old_host, validate=False): +def update_host_in_ring(ringbuilder, new_host, old_host, old_host_idx, + validate=False): if new_host.get('zone', 0) != old_host['zone']: devstr = DEVICE_KEY % new_host raise RingValidationError('Cannot update zone on %s, this can only be ' @@ -102,111 +58,115 @@ def update_host_in_ring(build_file, new_host, old_host, validate=False): if new_host_str != old_host_str: if not validate: - run_and_wait(rb_main, ["swift-ring-builder", build_file, - "set_info", old_host_str, - new_host_str]) + ringbuilder.devs[old_host_idx].update(new_host) + ringbuilder.devs_changed = True + ringbuilder.version += 1 except Exception as ex: raise RingValidationError(ex) if new_weight != old_weight and not validate: - change_host_weight(build_file, FULL_HOST_KEY % new_host, new_weight) + ringbuilder.set_dev_weight(ringbuilder.devs[old_host_idx]['id'], + new_weight) -def add_host_to_ring(build_file, host, validate=False): - host_str = "" +def add_host_to_ring(ringbuilder, host, validate=False): + new_host = {'region': 1, 'zone': 0, 'meta': ''} + new_host.update(host) try: - if host.get('region') is not None: - host_str += 'r%(region)d' % host - host_str += "z%(zone)d-" % host - host_str += FULL_HOST_KEY % host - weight = host.get('weight') + if validate: + ringbuilder.add_dev(new_host) except Exception as ex: raise RingValidationError(ex) - if not validate: - run_and_wait(rb_main, ["swift-ring-builder", build_file, 'add', - host_str, str(weight)]) - - -def run_and_wait(func, *args): - t = ThreadWithErr(target=func, args=args) - t.start() - t.join() - if t.err and t.err.code > 0: - sys.exit(t.err.code) - - -def get_build_file_data(build_file): - build_file_data = None - if exists(build_file): - try: - with open(build_file) as bf_stream: - build_file_data = pickle.load(bf_stream) - except Exception as ex: - print("Error: failed to load build file '%s': %s" % (build_file, - ex)) - build_file_data = None - return build_file_data def build_ring(build_name, repl, min_part_hours, part_power, hosts, region=None, validate=False, reset_mph_clock=False): # Create the build file build_file = "%s.builder" % build_name - build_file_data = get_build_file_data(build_file) + if exists(build_file): + ringbuilder = RingBuilder.load(build_file) + else: + ringbuilder = RingBuilder(part_power, repl, min_part_hours) - update = build_file_data is not None - create_buildfile( - build_file, - part_power, - repl, - min_part_hours, - update, - data=build_file_data, - validate=validate - ) + # run some checks + if repl != ringbuilder.replicas and not validate: + ringbuilder.set_replicas(repl) + + if min_part_hours != ringbuilder.min_part_hours and not validate: + ringbuilder.change_min_part_hours(min_part_hours) + + if part_power != ringbuilder.part_power: + raise RingValidationError( + 'Part power cannot be changed! you must rebuild the ring if you ' + 'need to change it.\nRing part power: %s Inventory part power: %s' + % (ringbuilder.part_power, part_power)) old_hosts = {} - if update: - for i, dev in enumerate(build_file_data['devs']): - if dev is not None: - if region is None or int(region) == int(dev['region']): - old_hosts[DEVICE_KEY % dev] = i + for i, dev in enumerate(ringbuilder.devs): + if dev is not None: + if region is None or int(region) == int(dev['region']): + old_hosts[DEVICE_KEY % dev] = i for host in hosts: host_key = DEVICE_KEY % host if region is None or int(region) == int(host['region']): if host_key in old_hosts: - old_host = build_file_data['devs'][old_hosts[host_key]] - update_host_in_ring(build_file, host, old_host, - validate=validate) + old_host = ringbuilder.devs[old_hosts[host_key]] + update_host_in_ring(ringbuilder, host, old_host, + old_hosts[host_key], validate=validate) old_hosts.pop(host_key) else: - add_host_to_ring(build_file, host, validate=validate) + add_host_to_ring(ringbuilder, host, validate=validate) if old_hosts and not validate: # There are still old hosts, these hosts must've been removed - for host in old_hosts: - remove_host_from_ring(build_file, host) + try: + for host, idx in old_hosts.items(): + ringbuilder.remove_dev(ringbuilder.devs[idx]['id']) + except Exception as ex: + raise RingValidationError(ex) + + build_file, ring_file = parse_builder_ring_filename_args(('', build_file)) + # serialise to disk before we think about writing the ring + backup_folder = join(dirname(build_file), 'backups') + try: + os.mkdir(backup_folder) + except OSError: + if not os.path.isdir(backup_folder): + raise + ts = time.time() + + ringbuilder.save(build_file) + ringbuilder.save(join(backup_folder, '%d.' % ts + basename(build_file))) # Rebalance ring if not validate: if not hosts: - run_and_wait( - rb_main, ["swift-ring-builder", build_file, "write_ring"] - ) + ringdata = ringbuilder.get_ring() + ringdata.save(join(backup_folder, '%d.' % ts + + basename(ring_file))) + ringdata.save(ring_file) else: if reset_mph_clock: - run_and_wait( - rb_main, ["swift-ring-builder", build_file, - "pretend_min_part_hours_passed"] - ) - run_and_wait( - rb_main, ["swift-ring-builder", build_file, "rebalance"] - ) - # In case no changes that require a rebalance have happened - # We may still need to write the ring for device changes. - run_and_wait( - rb_main, ["swift-ring-builder", build_file, "write_ring"] - ) + ringbuilder.pretend_min_part_hours_passed() + if ringbuilder.min_part_seconds_left > 0: + raise RingValidationError( + 'The time between rebalances must be at least ' + 'min_part_hours: %s hours (%s remaining)' % + (ringbuilder.min_part_hours, + timedelta(seconds=ringbuilder.min_part_seconds_left))) + exit(2) + parts, balance, removed_devs = ringbuilder.rebalance() + try: + ringbuilder.validate() + except Exception as ex: + raise RingValidationError(ex) + ringbuilder.save(join(backup_folder, '%d.' % ts + + basename(build_file))) + ringbuilder.save(build_file) + ringdata = ringbuilder.get_ring() + ringdata.save(join(backup_folder, '%d.' % ts + + basename(ring_file))) + ringdata.save(ring_file) def main(setup, region, reset_mph_clock):