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
168 lines
5.7 KiB
Django/Jinja
168 lines
5.7 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
|
|
|
|
import json
|
|
import pickle
|
|
import sys
|
|
|
|
USAGE = "usage: %prog -f <swift_ring.contentsa> -r <managed_region>"
|
|
|
|
DEVICE_KEY = "%(ip)s:%(port)d/%(device)s"
|
|
FULL_HOST_KEY = "%(ip)s:%(port)dR%(replication_ip)s:" \
|
|
"%(replication_port)d/%(device)s_w%(weight)d"
|
|
|
|
|
|
class RingComparisonError(Exception):
|
|
pass
|
|
|
|
|
|
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 check_ring_settings(build_file, part_power, repl, min_part_hours,
|
|
data=None):
|
|
# Check if the build file is emptuy
|
|
if data is None:
|
|
raise RingComparisonError('Build file %s is empty or does '
|
|
'not exist.' % build_file)
|
|
# Check if replica count matches for contents and ring file
|
|
if repl != data.get('replicas'):
|
|
raise RingComparisonError('Replica count does not match')
|
|
# Check min_part_hours matches for contents and ring file
|
|
if min_part_hours != data.get('min_part_hours'):
|
|
raise RingComparisonError('min_part_hours does not match')
|
|
# Check part_power matches for contents and ring file
|
|
if part_power != data.get('part_power'):
|
|
raise RingComparisonError('part_power does not match')
|
|
|
|
|
|
def check_host_settings(content_host, ring_host):
|
|
devstr = DEVICE_KEY % content_host
|
|
if content_host.get('zone', 0) != ring_host['zone']:
|
|
raise RingComparisonError('Zone on device %s differs to the ring.'
|
|
% devstr)
|
|
if content_host.get('region', 1) != ring_host['region']:
|
|
raise RingComparisonError('Region on device %s differs to the ring.'
|
|
% devstr)
|
|
|
|
content_host_str = FULL_HOST_KEY % content_host
|
|
ring_host_str = FULL_HOST_KEY % ring_host
|
|
|
|
if content_host_str != ring_host_str:
|
|
raise RingComparisonError('Content device %(content_host_str)s differs'
|
|
' to the ring device %(ring_host_str)s.')
|
|
|
|
|
|
def check_ring(build_name, repl, min_part_hours, part_power, content_hosts,
|
|
region=None):
|
|
build_file = "%s.builder" % build_name
|
|
build_file_data = get_build_file_data(build_file)
|
|
check_ring_settings(
|
|
build_file,
|
|
part_power,
|
|
repl,
|
|
min_part_hours,
|
|
data=build_file_data
|
|
)
|
|
|
|
ring_hosts = {}
|
|
for i, dev in enumerate(build_file_data['devs']):
|
|
if dev is not None:
|
|
if region is None or int(region) == int(dev['region']):
|
|
ring_hosts[DEVICE_KEY % dev] = i
|
|
for content_host in content_hosts:
|
|
host_key = DEVICE_KEY % content_host
|
|
if region is None or int(region) == int(content_host['region']):
|
|
if host_key in ring_hosts:
|
|
ring_host = build_file_data['devs'][ring_hosts[host_key]]
|
|
check_host_settings(content_host, ring_host)
|
|
ring_hosts.pop(host_key)
|
|
else:
|
|
raise RingComparisonError('Device %s is not in the ring.'
|
|
% host_key)
|
|
|
|
if ring_hosts:
|
|
for ring_host in ring_hosts:
|
|
if build_file_data['devs'][ring_hosts[ring_host]]['weight'] != 0:
|
|
raise RingComparisonError('There are devices in the ring that'
|
|
' are not in the inventory/contents'
|
|
' file.')
|
|
|
|
|
|
def main(setup, region):
|
|
# 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
|
|
|
|
content_hosts = _contents_file['drives']
|
|
kargs = {'content_hosts': content_hosts, 'region': region}
|
|
ring_call = [
|
|
_contents_file['builder_file'],
|
|
_contents_file['repl_number'],
|
|
_contents_file['min_part_hours'],
|
|
_contents_file['part_power']
|
|
]
|
|
|
|
try:
|
|
check_ring(*ring_call, **kargs)
|
|
print('SUCCESS: Ring is consistent with contents file')
|
|
except RingComparisonError as ex:
|
|
print(ex)
|
|
return 2
|
|
|
|
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"
|
|
)
|
|
|
|
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))
|