647b66a2ce
This patch adds the erasure code reconstructor. It follows the design of the replicator but: - There is no notion of update() or update_deleted(). - There is a single job processor - Jobs are processed partition by partition. - At the end of processing a rebalanced or handoff partition, the reconstructor will remove successfully reverted objects if any. And various ssync changes such as the addition of reconstruct_fa() function called from ssync_sender which performs the actual reconstruction while sending the object to the receiver Co-Authored-By: Alistair Coles <alistair.coles@hp.com> Co-Authored-By: Thiago da Silva <thiago@redhat.com> Co-Authored-By: John Dickinson <me@not.mn> Co-Authored-By: Clay Gerrard <clay.gerrard@gmail.com> Co-Authored-By: Tushar Gohad <tushar.gohad@intel.com> Co-Authored-By: Samuel Merritt <sam@swiftstack.com> Co-Authored-By: Christian Schwede <christian.schwede@enovance.com> Co-Authored-By: Yuan Zhou <yuan.zhou@intel.com> blueprint ec-reconstructor Change-Id: I7d15620dc66ee646b223bb9fff700796cd6bef51
237 lines
7.9 KiB
Python
237 lines
7.9 KiB
Python
#!/usr/bin/python -u
|
|
# 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.
|
|
|
|
import sys
|
|
import itertools
|
|
import uuid
|
|
from optparse import OptionParser
|
|
from urlparse import urlparse
|
|
import random
|
|
|
|
from swift.common.manager import Manager
|
|
from swift.common import utils, ring
|
|
from swift.common.storage_policy import POLICIES
|
|
from swift.common.http import HTTP_NOT_FOUND
|
|
|
|
from swiftclient import client, get_auth, ClientException
|
|
|
|
from test.probe.common import ENABLED_POLICIES
|
|
|
|
TIMEOUT = 60
|
|
|
|
|
|
def meta_command(name, bases, attrs):
|
|
"""
|
|
Look for attrs with a truthy attribute __command__ and add them to an
|
|
attribute __commands__ on the type that maps names to decorated methods.
|
|
The decorated methods' doc strings also get mapped in __docs__.
|
|
|
|
Also adds a method run(command_name, *args, **kwargs) that will
|
|
execute the method mapped to the name in __commands__.
|
|
"""
|
|
commands = {}
|
|
docs = {}
|
|
for attr, value in attrs.items():
|
|
if getattr(value, '__command__', False):
|
|
commands[attr] = value
|
|
# methods have always have a __doc__ attribute, sometimes empty
|
|
docs[attr] = (getattr(value, '__doc__', None) or
|
|
'perform the %s command' % attr).strip()
|
|
attrs['__commands__'] = commands
|
|
attrs['__docs__'] = docs
|
|
|
|
def run(self, command, *args, **kwargs):
|
|
return self.__commands__[command](self, *args, **kwargs)
|
|
attrs.setdefault('run', run)
|
|
return type(name, bases, attrs)
|
|
|
|
|
|
def command(f):
|
|
f.__command__ = True
|
|
return f
|
|
|
|
|
|
class BrainSplitter(object):
|
|
|
|
__metaclass__ = meta_command
|
|
|
|
def __init__(self, url, token, container_name='test', object_name='test',
|
|
server_type='container', policy=None):
|
|
self.url = url
|
|
self.token = token
|
|
self.account = utils.split_path(urlparse(url).path, 2, 2)[1]
|
|
self.container_name = container_name
|
|
self.object_name = object_name
|
|
server_list = ['%s-server' % server_type] if server_type else ['all']
|
|
self.servers = Manager(server_list)
|
|
policies = list(ENABLED_POLICIES)
|
|
random.shuffle(policies)
|
|
self.policies = itertools.cycle(policies)
|
|
|
|
o = object_name if server_type == 'object' else None
|
|
c = container_name if server_type in ('object', 'container') else None
|
|
if server_type in ('container', 'account'):
|
|
if policy:
|
|
raise TypeError('Metadata server brains do not '
|
|
'support specific storage policies')
|
|
self.policy = None
|
|
self.ring = ring.Ring(
|
|
'/etc/swift/%s.ring.gz' % server_type)
|
|
elif server_type == 'object':
|
|
if not policy:
|
|
raise TypeError('Object BrainSplitters need to '
|
|
'specify the storage policy')
|
|
self.policy = policy
|
|
policy.load_ring('/etc/swift')
|
|
self.ring = policy.object_ring
|
|
else:
|
|
raise ValueError('Unkonwn server_type: %r' % server_type)
|
|
self.server_type = server_type
|
|
|
|
part, nodes = self.ring.get_nodes(self.account, c, o)
|
|
|
|
node_ids = [n['id'] for n in nodes]
|
|
if all(n_id in node_ids for n_id in (0, 1)):
|
|
self.primary_numbers = (1, 2)
|
|
self.handoff_numbers = (3, 4)
|
|
else:
|
|
self.primary_numbers = (3, 4)
|
|
self.handoff_numbers = (1, 2)
|
|
|
|
@command
|
|
def start_primary_half(self):
|
|
"""
|
|
start servers 1 & 2
|
|
"""
|
|
tuple(self.servers.start(number=n) for n in self.primary_numbers)
|
|
|
|
@command
|
|
def stop_primary_half(self):
|
|
"""
|
|
stop servers 1 & 2
|
|
"""
|
|
tuple(self.servers.stop(number=n) for n in self.primary_numbers)
|
|
|
|
@command
|
|
def start_handoff_half(self):
|
|
"""
|
|
start servers 3 & 4
|
|
"""
|
|
tuple(self.servers.start(number=n) for n in self.handoff_numbers)
|
|
|
|
@command
|
|
def stop_handoff_half(self):
|
|
"""
|
|
stop servers 3 & 4
|
|
"""
|
|
tuple(self.servers.stop(number=n) for n in self.handoff_numbers)
|
|
|
|
@command
|
|
def put_container(self, policy_index=None):
|
|
"""
|
|
put container with next storage policy
|
|
"""
|
|
policy = self.policies.next()
|
|
if policy_index is not None:
|
|
policy = POLICIES.get_by_index(int(policy_index))
|
|
if not policy:
|
|
raise ValueError('Unknown policy with index %s' % policy)
|
|
headers = {'X-Storage-Policy': policy.name}
|
|
client.put_container(self.url, self.token, self.container_name,
|
|
headers=headers)
|
|
|
|
@command
|
|
def delete_container(self):
|
|
"""
|
|
delete container
|
|
"""
|
|
client.delete_container(self.url, self.token, self.container_name)
|
|
|
|
@command
|
|
def put_object(self, headers=None):
|
|
"""
|
|
issue put for zero byte test object
|
|
"""
|
|
client.put_object(self.url, self.token, self.container_name,
|
|
self.object_name, headers=headers)
|
|
|
|
@command
|
|
def delete_object(self):
|
|
"""
|
|
issue delete for test object
|
|
"""
|
|
try:
|
|
client.delete_object(self.url, self.token, self.container_name,
|
|
self.object_name)
|
|
except ClientException as err:
|
|
if err.http_status != HTTP_NOT_FOUND:
|
|
raise
|
|
|
|
parser = OptionParser('%prog [options] '
|
|
'<command>[:<args>[,<args>...]] [<command>...]')
|
|
parser.usage += '\n\nCommands:\n\t' + \
|
|
'\n\t'.join("%s - %s" % (name, doc) for name, doc in
|
|
BrainSplitter.__docs__.items())
|
|
parser.add_option('-c', '--container', default='container-%s' % uuid.uuid4(),
|
|
help='set container name')
|
|
parser.add_option('-o', '--object', default='object-%s' % uuid.uuid4(),
|
|
help='set object name')
|
|
parser.add_option('-s', '--server_type', default='container',
|
|
help='set server type')
|
|
parser.add_option('-P', '--policy_name', default=None,
|
|
help='set policy')
|
|
|
|
|
|
def main():
|
|
options, commands = parser.parse_args()
|
|
if not commands:
|
|
parser.print_help()
|
|
return 'ERROR: must specify at least one command'
|
|
for cmd_args in commands:
|
|
cmd = cmd_args.split(':', 1)[0]
|
|
if cmd not in BrainSplitter.__commands__:
|
|
parser.print_help()
|
|
return 'ERROR: unknown command %s' % cmd
|
|
url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
|
|
'test:tester', 'testing')
|
|
if options.server_type == 'object' and not options.policy_name:
|
|
options.policy_name = POLICIES.default.name
|
|
if options.policy_name:
|
|
options.server_type = 'object'
|
|
policy = POLICIES.get_by_name(options.policy_name)
|
|
if not policy:
|
|
return 'ERROR: unknown policy %r' % options.policy_name
|
|
else:
|
|
policy = None
|
|
brain = BrainSplitter(url, token, options.container, options.object,
|
|
options.server_type, policy=policy)
|
|
for cmd_args in commands:
|
|
parts = cmd_args.split(':', 1)
|
|
command = parts[0]
|
|
if len(parts) > 1:
|
|
args = utils.list_from_csv(parts[1])
|
|
else:
|
|
args = ()
|
|
try:
|
|
brain.run(command, *args)
|
|
except ClientException as e:
|
|
print '**WARNING**: %s raised %s' % (command, e)
|
|
print 'STATUS'.join(['*' * 25] * 2)
|
|
brain.servers.status()
|
|
sys.exit()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|