openstack-ansible-os_swift/templates/swift_rings.py.j2
Andy McCrae 750ccaa9e8 Refactor and improve swift-rings.py
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
2016-09-02 05:10:17 +00:00

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))