4286f36a60
This patch takes a first step towards support for object system metadata by enabling headers in the x-object-sysmeta- namespace to be persisted when objects are PUT. This should be useful for other pending patches such as on demand migration and server side encryption (https://review.openstack.org/#/c/64430/ and https://review.openstack.org/#/c/76578/1). The x-object-sysmeta- namespace is already reserved/protected by the gatekeeper and passed through the proxy. This patch modifies the object server to persist these headers alongside user metadata when an object is PUT. This patch will preserve existing object system metadata and ignore any new system metadata when handling object POSTs, including POST-as-copy operations. Support for modification of object system metadata with a POST request requires further work as discussed in the blueprint. This patch will preserve existing object system metadata and update it with new system metadata when copying an object. A new probe test is added which makes use of the BrainSplitter class that has been moved from test_container_merge_policy_index.py to a new module brain.py. blueprint object-system-metadata Change-Id: If716bc15730b7322266ebff4ab8dd31e78e4b962
207 lines
6.7 KiB
Python
207 lines
6.7 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
|
|
|
|
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')
|
|
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')
|
|
|
|
|
|
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')
|
|
brain = BrainSplitter(url, token, options.container, options.object,
|
|
options.server_type)
|
|
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())
|