46 changed files with 1959 additions and 335 deletions
@ -0,0 +1,206 @@
|
||||
#!/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 |
||||
|
||||
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'): |
||||
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(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 |
||||
part, nodes = ring.Ring( |
||||
'/etc/swift/%s.ring.gz' % server_type).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') |
||||
< |