750ccaa9e8
We can simplify and refactor swift_rings.py and swift_rings_check.py by moving to a "FULL_HOST_KEY" model where we simply compare the full string, rather than individual elements. To do this we need to adjust the contents template to use the same field values as used by swift: * rename repl_ip to replication_ip * rename repl_port to replication_port Additionally, this allows us the ability to change port values on the fly, by adjusting the "DEVICE_KEY" to only be the IP and device name the port will now automatically get changed if the service port changes. This is a precursor to adjusting the default swift storage service ports to match upstream defaults, and will reduce the upgrade impact of that task. Change-Id: I704edcba4facb2170990ebec2a67d4179a023fc2
263 lines
8.8 KiB
Django/Jinja
263 lines
8.8 KiB
Django/Jinja
#!{{ swift_bin }}/python
|
|
# Copyright 2014, Rackspace US, Inc.
|
|
#
|
|
# Licensed 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.
|
|
|
|
from __future__ import print_function
|
|
from optparse import OptionParser
|
|
from os.path import exists
|
|
|
|
from swift.cli.ringbuilder import main as rb_main
|
|
|
|
import json
|
|
import pickle
|
|
import sys
|
|
import threading
|
|
|
|
|
|
USAGE = "usage: %prog -f <swift_ring.contents> -r <managed_region>"
|
|
|
|
DEVICE_KEY = "%(ip)s/%(device)s"
|
|
FULL_HOST_KEY = "%(ip)s:%(port)dR%(replication_ip)s:" \
|
|
"%(replication_port)d/%(device)s"
|
|
|
|
|
|
class RingValidationError(Exception):
|
|
pass
|
|
|
|
|
|
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):
|
|
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 '
|
|
'done when the drive is added' % devstr)
|
|
if new_host.get('region', 1) != old_host['region']:
|
|
devstr = DEVICE_KEY % new_host
|
|
raise RingValidationError('Cannot update region on %s, this can only '
|
|
'be done when the drive is added' % devstr)
|
|
|
|
try:
|
|
old_host_str = FULL_HOST_KEY % old_host
|
|
new_host_str = FULL_HOST_KEY % new_host
|
|
|
|
new_weight = new_host.get('weight')
|
|
old_weight = old_host.get('weight')
|
|
|
|
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])
|
|
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)
|
|
|
|
|
|
def add_host_to_ring(build_file, host, validate=False):
|
|
host_str = ""
|
|
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')
|
|
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 = threading.Thread(target=func, args=args)
|
|
t.start()
|
|
return t.join()
|
|
|
|
|
|
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)
|
|
|
|
update = build_file_data is not None
|
|
create_buildfile(
|
|
build_file,
|
|
part_power,
|
|
repl,
|
|
min_part_hours,
|
|
update,
|
|
data=build_file_data,
|
|
validate=validate
|
|
)
|
|
|
|
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 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_hosts.pop(host_key)
|
|
else:
|
|
add_host_to_ring(build_file, 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)
|
|
|
|
# Rebalance ring
|
|
if not validate:
|
|
if not hosts:
|
|
run_and_wait(
|
|
rb_main, ["swift-ring-builder", build_file, "write_ring"]
|
|
)
|
|
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"]
|
|
)
|
|
|
|
|
|
def main(setup, region, reset_mph_clock):
|
|
# load the json file
|
|
try:
|
|
with open(setup) as json_stream:
|
|
_contents_file = json.load(json_stream)
|
|
except Exception as ex:
|
|
print("Failed to load json string %s" % ex)
|
|
return 1
|
|
|
|
hosts = _contents_file['drives']
|
|
kargs = {'validate': True, 'hosts': hosts, 'region': region,
|
|
'reset_mph_clock': reset_mph_clock}
|
|
ring_call = [
|
|
_contents_file['builder_file'],
|
|
_contents_file['repl_number'],
|
|
_contents_file['min_part_hours'],
|
|
_contents_file['part_power']
|
|
]
|
|
|
|
try:
|
|
build_ring(*ring_call, **kargs)
|
|
except RingValidationError as ex:
|
|
print(ex)
|
|
return 2
|
|
|
|
# If the validation passes lets go ahead and build the rings.
|
|
kargs.pop('validate')
|
|
build_ring(*ring_call, **kargs)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = OptionParser(USAGE)
|
|
parser.add_option(
|
|
"-f",
|
|
"--file",
|
|
dest="setup",
|
|
help="Specify the swift ring contents file.",
|
|
metavar="FILE"
|
|
)
|
|
parser.add_option(
|
|
"-r",
|
|
"--region",
|
|
help="Specify the region to manage for the ring file.",
|
|
dest="region",
|
|
type='int',
|
|
metavar="REGION"
|
|
)
|
|
parser.add_option(
|
|
"-p",
|
|
"--pretend_min_part_hours_passed",
|
|
help="Reset the clock on the last time a rebalance happened.",
|
|
dest="reset_mph_clock",
|
|
action="store_true",
|
|
default=False
|
|
)
|
|
|
|
options, _args = parser.parse_args(sys.argv[1:])
|
|
if options.setup and not exists(options.setup):
|
|
print("Swift ring contents file not found or doesn't exist")
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
sys.exit(main(options.setup, options.region, options.reset_mph_clock))
|