Add support for storage policies to have more than one name
This patch alters storage_policy.py to allow storage policies to have multiple names. Now users are able to add a number of human-readable aliases for storage policies. Policies now have a .name (the default name), .aliases (a string of comma seperated aliases), and .aliases_list (a list of all human readable names). Policies will always have an .aliases value if no aliases are set it will contain the default name. The policy docs and tests have been updated to reflect changes and policy.get_policy_info has been altered to display the name and aliases Change-Id: I02967ca8d7c790595e5ee551581196aa64552eea
This commit is contained in:
parent
09133f5bd9
commit
211758f8cb
@ -57,7 +57,7 @@ deployers. Each container has a new special immutable metadata element called
|
||||
the storage policy index. Note that internally, Swift relies on policy
|
||||
indexes and not policy names. Policy names exist for human readability and
|
||||
translation is managed in the proxy. When a container is created, one new
|
||||
optional header is supported to specify the policy name. If nothing is
|
||||
optional header is supported to specify the policy name. If no name is
|
||||
specified, the default policy is used (and if no other policies defined,
|
||||
Policy-0 is considered the default). We will be covering the difference
|
||||
between default and Policy-0 in the next section.
|
||||
@ -170,12 +170,13 @@ Storage Policies is a versatile feature intended to support both new and
|
||||
pre-existing clusters with the same level of flexibility. For that reason, we
|
||||
introduce the ``Policy-0`` concept which is not the same as the "default"
|
||||
policy. As you will see when we begin to configure policies, each policy has
|
||||
both a name (human friendly, configurable) as well as an index (or simply
|
||||
policy number). Swift reserves index 0 to map to the object ring that's
|
||||
present in all installations (e.g., ``/etc/swift/object.ring.gz``). You can
|
||||
name this policy anything you like, and if no policies are defined it will
|
||||
report itself as ``Policy-0``, however you cannot change the index as there must
|
||||
always be a policy with index 0.
|
||||
a single name and an arbitrary number of aliases (human friendly,
|
||||
configurable) as well as an index (or simply policy number). Swift reserves
|
||||
index 0 to map to the object ring that's present in all installations
|
||||
(e.g., ``/etc/swift/object.ring.gz``). You can name this policy anything you
|
||||
like, and if no policies are defined it will report itself as ``Policy-0``,
|
||||
however you cannot change the index as there must always be a policy with
|
||||
index 0.
|
||||
|
||||
Another important concept is the default policy which can be any policy
|
||||
in the cluster. The default policy is the policy that is automatically
|
||||
@ -273,6 +274,8 @@ file:
|
||||
* Policy names must contain only letters, digits or a dash
|
||||
* Policy names must be unique
|
||||
* The policy name 'Policy-0' can only be used for the policy with index 0
|
||||
* Multiple names can be assigned to one policy using aliases. All names
|
||||
must follow the Swift naming rules.
|
||||
* If any policies are defined, exactly one policy must be declared default
|
||||
* Deprecated policies cannot be declared the default
|
||||
* If no ``policy_type`` is provided, ``replication`` is the default value.
|
||||
@ -288,6 +291,7 @@ example configuration.::
|
||||
|
||||
[storage-policy:0]
|
||||
name = gold
|
||||
aliases = yellow, orange
|
||||
policy_type = replication
|
||||
default = yes
|
||||
|
||||
@ -301,8 +305,10 @@ information about the ``default`` and ``deprecated`` options.
|
||||
|
||||
There are some other considerations when managing policies:
|
||||
|
||||
* Policy names can be changed (but be sure that users are aware, aliases are
|
||||
not currently supported but could be implemented in custom middleware!)
|
||||
* Policy names can be changed.
|
||||
* Aliases are supported and can be added and removed. If the primary name
|
||||
of a policy is removed the next available alias will be adopted as the
|
||||
primary name. A policy must always have at least one name.
|
||||
* You cannot change the index of a policy once it has been created
|
||||
* The default policy can be changed at any time, by adding the
|
||||
default directive to the desired policy section
|
||||
@ -399,7 +405,7 @@ The module, :ref:`storage_policy`, is responsible for parsing the
|
||||
configured policies via class :class:`.StoragePolicyCollection`. This
|
||||
collection is made up of policies of class :class:`.StoragePolicy`. The
|
||||
collection class includes handy functions for getting to a policy either by
|
||||
name or by index , getting info about the policies, etc. There's also one
|
||||
name or by index , getting info about the policies, etc. There's also one
|
||||
very important function, :meth:`~.StoragePolicyCollection.get_object_ring`.
|
||||
Object rings are members of the :class:`.StoragePolicy` class and are
|
||||
actually not instantiated until the :meth:`~.StoragePolicy.load_ring`
|
||||
|
4
doc/source/policies_saio.rst
Normal file → Executable file
4
doc/source/policies_saio.rst
Normal file → Executable file
@ -26,6 +26,7 @@ to implement a usable set of policies.
|
||||
|
||||
[storage-policy:0]
|
||||
name = gold
|
||||
aliases = yellow, orange
|
||||
default = yes
|
||||
|
||||
[storage-policy:1]
|
||||
@ -82,7 +83,8 @@ Storage Policies effect placement of data in Swift.
|
||||
|
||||
You should see this: (only showing the policy output here)::
|
||||
|
||||
policies: [{'default': True, 'name': 'gold'}, {'name': 'silver'}]
|
||||
policies: [{'aliases': 'gold, yellow, orange', 'default': True,
|
||||
'name': 'gold'}, {'aliases': 'silver', 'name': 'silver'}]
|
||||
|
||||
3. Now create a container without specifying a policy, it will use the
|
||||
default, 'gold' and then put a test object in it (create the file ``file0.txt``
|
||||
|
15
etc/swift.conf-sample
Normal file → Executable file
15
etc/swift.conf-sample
Normal file → Executable file
@ -21,7 +21,7 @@ swift_hash_path_prefix = changeme
|
||||
# policy with index 0 will be declared the default. If multiple policies are
|
||||
# defined you must define a policy with index 0 and you must specify a
|
||||
# default. It is recommended you always define a section for
|
||||
# storage-policy:0.
|
||||
# storage-policy:0. Aliases are not required when defining a storage policy.
|
||||
#
|
||||
# A 'policy_type' argument is also supported but is not mandatory. Default
|
||||
# policy type 'replication' is used when 'policy_type' is unspecified.
|
||||
@ -29,6 +29,7 @@ swift_hash_path_prefix = changeme
|
||||
name = Policy-0
|
||||
default = yes
|
||||
#policy_type = replication
|
||||
aliases = yellow, orange
|
||||
|
||||
# the following section would declare a policy called 'silver', the number of
|
||||
# replicas will be determined by how the ring is built. In this example the
|
||||
@ -40,7 +41,10 @@ default = yes
|
||||
# this config has specified it as the default. However if a legacy container
|
||||
# (one created with a pre-policy version of swift) is accessed, it is known
|
||||
# implicitly to be assigned to the policy with index 0 as opposed to the
|
||||
# current default.
|
||||
# current default. Note that even without specifying any aliases, a policy
|
||||
# always has at least the default name stored in aliases because this field is
|
||||
# used to contain all human readable names for a storage policy.
|
||||
#
|
||||
#[storage-policy:1]
|
||||
#name = silver
|
||||
#policy_type = replication
|
||||
@ -67,12 +71,13 @@ default = yes
|
||||
# refer to Swift documentation for details on how to configure EC policies.
|
||||
#
|
||||
# The example 'deepfreeze10-4' policy defined below is a _sample_
|
||||
# configuration with 10 'data' and 4 'parity' fragments. 'ec_type'
|
||||
# defines the Erasure Coding scheme. 'jerasure_rs_vand' (Reed-Solomon
|
||||
# Vandermonde) is used as an example below.
|
||||
# configuration with an alias of 'df10-4' as well as 10 'data' and 4 'parity'
|
||||
# fragments. 'ec_type' defines the Erasure Coding scheme.
|
||||
# 'jerasure_rs_vand' (Reed-Solomon Vandermonde) is used as an example below.
|
||||
#
|
||||
#[storage-policy:2]
|
||||
#name = deepfreeze10-4
|
||||
#aliases = df10-4
|
||||
#policy_type = erasure_coding
|
||||
#ec_type = jerasure_rs_vand
|
||||
#ec_num_data_fragments = 10
|
||||
|
199
swift/common/storage_policy.py
Normal file → Executable file
199
swift/common/storage_policy.py
Normal file → Executable file
@ -16,11 +16,9 @@ import os
|
||||
import string
|
||||
import textwrap
|
||||
import six
|
||||
|
||||
from six.moves.configparser import ConfigParser
|
||||
|
||||
from swift.common.utils import (
|
||||
config_true_value, SWIFT_CONF_FILE, whataremyips)
|
||||
config_true_value, SWIFT_CONF_FILE, whataremyips, list_from_csv)
|
||||
from swift.common.ring import Ring, RingData
|
||||
from swift.common.utils import quorum_size
|
||||
from swift.common.exceptions import RingValidationError
|
||||
@ -84,7 +82,6 @@ class BindPortsCache(object):
|
||||
|
||||
|
||||
class PolicyError(ValueError):
|
||||
|
||||
def __init__(self, msg, index=None):
|
||||
if index is not None:
|
||||
msg += ', for index %r' % index
|
||||
@ -161,7 +158,7 @@ class BaseStoragePolicy(object):
|
||||
policy_type_to_policy_cls = {}
|
||||
|
||||
def __init__(self, idx, name='', is_default=False, is_deprecated=False,
|
||||
object_ring=None):
|
||||
object_ring=None, aliases=''):
|
||||
# do not allow BaseStoragePolicy class to be instantiated directly
|
||||
if type(self) == BaseStoragePolicy:
|
||||
raise TypeError("Can't instantiate BaseStoragePolicy directly")
|
||||
@ -172,18 +169,17 @@ class BaseStoragePolicy(object):
|
||||
raise PolicyError('Invalid index', idx)
|
||||
if self.idx < 0:
|
||||
raise PolicyError('Invalid index', idx)
|
||||
if not name:
|
||||
self.alias_list = []
|
||||
if not name or not self._validate_policy_name(name):
|
||||
raise PolicyError('Invalid name %r' % name, idx)
|
||||
# this is defensively restrictive, but could be expanded in the future
|
||||
if not all(c in VALID_CHARS for c in name):
|
||||
raise PolicyError('Names are used as HTTP headers, and can not '
|
||||
'reliably contain any characters not in %r. '
|
||||
'Invalid name %r' % (VALID_CHARS, name))
|
||||
if name.upper() == LEGACY_POLICY_NAME.upper() and self.idx != 0:
|
||||
msg = 'The name %s is reserved for policy index 0. ' \
|
||||
'Invalid name %r' % (LEGACY_POLICY_NAME, name)
|
||||
raise PolicyError(msg, idx)
|
||||
self.name = name
|
||||
self.alias_list.append(name)
|
||||
if aliases:
|
||||
names_list = list_from_csv(aliases)
|
||||
for alias in names_list:
|
||||
if alias == name:
|
||||
continue
|
||||
self._validate_policy_name(alias)
|
||||
self.alias_list.append(alias)
|
||||
self.is_deprecated = config_true_value(is_deprecated)
|
||||
self.is_default = config_true_value(is_default)
|
||||
if self.policy_type not in BaseStoragePolicy.policy_type_to_policy_cls:
|
||||
@ -191,9 +187,23 @@ class BaseStoragePolicy(object):
|
||||
if self.is_deprecated and self.is_default:
|
||||
raise PolicyError('Deprecated policy can not be default. '
|
||||
'Invalid config', self.idx)
|
||||
|
||||
self.ring_name = _get_policy_string('object', self.idx)
|
||||
self.object_ring = object_ring
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.alias_list[0]
|
||||
|
||||
@name.setter
|
||||
def name_setter(self, name):
|
||||
self._validate_policy_name(name)
|
||||
self.alias_list[0] = name
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
return ", ".join(self.alias_list)
|
||||
|
||||
def __int__(self):
|
||||
return self.idx
|
||||
|
||||
@ -203,8 +213,8 @@ class BaseStoragePolicy(object):
|
||||
def __repr__(self):
|
||||
return ("%s(%d, %r, is_default=%s, "
|
||||
"is_deprecated=%s, policy_type=%r)") % \
|
||||
(self.__class__.__name__, self.idx, self.name,
|
||||
self.is_default, self.is_deprecated, self.policy_type)
|
||||
(self.__class__.__name__, self.idx, self.alias_list,
|
||||
self.is_default, self.is_deprecated, self.policy_type)
|
||||
|
||||
@classmethod
|
||||
def register(cls, policy_type):
|
||||
@ -213,6 +223,7 @@ class BaseStoragePolicy(object):
|
||||
their StoragePolicy class. This will also set the policy_type
|
||||
attribute on the registered implementation.
|
||||
"""
|
||||
|
||||
def register_wrapper(policy_cls):
|
||||
if policy_type in cls.policy_type_to_policy_cls:
|
||||
raise PolicyError(
|
||||
@ -222,6 +233,7 @@ class BaseStoragePolicy(object):
|
||||
cls.policy_type_to_policy_cls[policy_type] = policy_cls
|
||||
policy_cls.policy_type = policy_type
|
||||
return policy_cls
|
||||
|
||||
return register_wrapper
|
||||
|
||||
@classmethod
|
||||
@ -231,6 +243,7 @@ class BaseStoragePolicy(object):
|
||||
"""
|
||||
return {
|
||||
'name': 'name',
|
||||
'aliases': 'aliases',
|
||||
'policy_type': 'policy_type',
|
||||
'default': 'is_default',
|
||||
'deprecated': 'is_deprecated',
|
||||
@ -269,6 +282,77 @@ class BaseStoragePolicy(object):
|
||||
info.pop('policy_type')
|
||||
return info
|
||||
|
||||
def _validate_policy_name(self, name):
|
||||
"""
|
||||
Helper function to determine the validity of a policy name. Used
|
||||
to check policy names before setting them.
|
||||
|
||||
:param name: a name string for a single policy name.
|
||||
:returns: true if the name is valid.
|
||||
:raises: PolicyError if the policy name is invalid.
|
||||
"""
|
||||
# this is defensively restrictive, but could be expanded in the future
|
||||
if not all(c in VALID_CHARS for c in name):
|
||||
raise PolicyError('Names are used as HTTP headers, and can not '
|
||||
'reliably contain any characters not in %r. '
|
||||
'Invalid name %r' % (VALID_CHARS, name))
|
||||
if name.upper() == LEGACY_POLICY_NAME.upper() and self.idx != 0:
|
||||
msg = 'The name %s is reserved for policy index 0. ' \
|
||||
'Invalid name %r' % (LEGACY_POLICY_NAME, name)
|
||||
raise PolicyError(msg, self.idx)
|
||||
if name.upper() in (existing_name.upper() for existing_name
|
||||
in self.alias_list):
|
||||
msg = 'The name %s is already assigned to this policy.' % name
|
||||
raise PolicyError(msg, self.idx)
|
||||
|
||||
return True
|
||||
|
||||
def add_name(self, name):
|
||||
"""
|
||||
Adds an alias name to the storage policy. Shouldn't be called
|
||||
directly from the storage policy but instead through the
|
||||
storage policy collection class, so lookups by name resolve
|
||||
correctly.
|
||||
|
||||
:param name: a new alias for the storage policy
|
||||
"""
|
||||
if self._validate_policy_name(name):
|
||||
self.alias_list.append(name)
|
||||
|
||||
def remove_name(self, name):
|
||||
"""
|
||||
Removes an alias name from the storage policy. Shouldn't be called
|
||||
directly from the storage policy but instead through the storage
|
||||
policy collection class, so lookups by name resolve correctly. If
|
||||
the name removed is the primary name then the next availiable alias
|
||||
will be adopted as the new primary name.
|
||||
|
||||
:param name: a name assigned to the storage policy
|
||||
"""
|
||||
if name not in self.alias_list:
|
||||
raise PolicyError("%s is not a name assigned to policy %s"
|
||||
% (name, self.idx))
|
||||
if len(self.alias_list) == 1:
|
||||
raise PolicyError("Cannot remove only name %s from policy %s. "
|
||||
"Policies must have at least one name."
|
||||
% (name, self.idx))
|
||||
else:
|
||||
self.alias_list.remove(name)
|
||||
|
||||
def change_primary_name(self, name):
|
||||
"""
|
||||
Changes the primary/default name of the policy to a specified name.
|
||||
|
||||
:param name: a string name to replace the current primary name.
|
||||
"""
|
||||
if name == self.name:
|
||||
return
|
||||
elif name in self.alias_list:
|
||||
self.remove_name(name)
|
||||
else:
|
||||
self._validate_policy_name(name)
|
||||
self.alias_list.insert(0, name)
|
||||
|
||||
def _validate_ring(self):
|
||||
"""
|
||||
Hook, called when the ring is loaded. Can be used to
|
||||
@ -329,13 +413,15 @@ class ECStoragePolicy(BaseStoragePolicy):
|
||||
:func:`~swift.common.storage_policy.reload_storage_policies` to load
|
||||
POLICIES from ``swift.conf``.
|
||||
"""
|
||||
def __init__(self, idx, name='', is_default=False,
|
||||
|
||||
def __init__(self, idx, name='', aliases='', is_default=False,
|
||||
is_deprecated=False, object_ring=None,
|
||||
ec_segment_size=DEFAULT_EC_OBJECT_SEGMENT_SIZE,
|
||||
ec_type=None, ec_ndata=None, ec_nparity=None):
|
||||
|
||||
super(ECStoragePolicy, self).__init__(
|
||||
idx, name, is_default, is_deprecated, object_ring)
|
||||
idx=idx, name=name, aliases=aliases, is_default=is_default,
|
||||
is_deprecated=is_deprecated, object_ring=object_ring)
|
||||
|
||||
# Validate erasure_coding policy specific members
|
||||
# ec_type is one of the EC implementations supported by PyEClib
|
||||
@ -441,9 +527,9 @@ class ECStoragePolicy(BaseStoragePolicy):
|
||||
|
||||
def __repr__(self):
|
||||
return ("%s, EC config(ec_type=%s, ec_segment_size=%d, "
|
||||
"ec_ndata=%d, ec_nparity=%d)") % (
|
||||
super(ECStoragePolicy, self).__repr__(), self.ec_type,
|
||||
self.ec_segment_size, self.ec_ndata, self.ec_nparity)
|
||||
"ec_ndata=%d, ec_nparity=%d)") % \
|
||||
(super(ECStoragePolicy, self).__repr__(), self.ec_type,
|
||||
self.ec_segment_size, self.ec_ndata, self.ec_nparity)
|
||||
|
||||
@classmethod
|
||||
def _config_options_map(cls):
|
||||
@ -532,6 +618,7 @@ class StoragePolicyCollection(object):
|
||||
* Deprecated policies can not be declared the default
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, pols):
|
||||
self.default = []
|
||||
self.by_name = {}
|
||||
@ -542,7 +629,8 @@ class StoragePolicyCollection(object):
|
||||
"""
|
||||
Add pre-validated policies to internal indexes.
|
||||
"""
|
||||
self.by_name[policy.name.upper()] = policy
|
||||
for name in policy.alias_list:
|
||||
self.by_name[name.upper()] = policy
|
||||
self.by_index[int(policy)] = policy
|
||||
|
||||
def __repr__(self):
|
||||
@ -570,9 +658,10 @@ class StoragePolicyCollection(object):
|
||||
if int(policy) in self.by_index:
|
||||
raise PolicyError('Duplicate index %s conflicts with %s' % (
|
||||
policy, self.get_by_index(int(policy))))
|
||||
if policy.name.upper() in self.by_name:
|
||||
raise PolicyError('Duplicate name %s conflicts with %s' % (
|
||||
policy, self.get_by_name(policy.name)))
|
||||
for name in policy.alias_list:
|
||||
if name.upper() in self.by_name:
|
||||
raise PolicyError('Duplicate name %s conflicts with %s' % (
|
||||
policy, self.get_by_name(name)))
|
||||
if policy.is_default:
|
||||
if not self.default:
|
||||
self.default = policy
|
||||
@ -667,6 +756,62 @@ class StoragePolicyCollection(object):
|
||||
policy_info.append(policy_entry)
|
||||
return policy_info
|
||||
|
||||
def add_policy_alias(self, policy_index, *aliases):
|
||||
"""
|
||||
Adds a new name or names to a policy
|
||||
|
||||
:param policy_index: index of a policy in this policy collection.
|
||||
:param *aliases: arbitrary number of string policy names to add.
|
||||
"""
|
||||
policy = self.get_by_index(policy_index)
|
||||
for alias in aliases:
|
||||
if alias.upper() in self.by_name:
|
||||
raise PolicyError('Duplicate name %s in use '
|
||||
'by policy %s' % (alias,
|
||||
self.get_by_name(alias)))
|
||||
else:
|
||||
policy.add_name(alias)
|
||||
self.by_name[alias.upper()] = policy
|
||||
|
||||
def remove_policy_alias(self, *aliases):
|
||||
"""
|
||||
Removes a name or names from a policy. If the name removed is the
|
||||
primary name then the next availiable alias will be adopted
|
||||
as the new primary name.
|
||||
|
||||
:param *aliases: arbitrary number of existing policy names to remove.
|
||||
"""
|
||||
for alias in aliases:
|
||||
policy = self.get_by_name(alias)
|
||||
if not policy:
|
||||
raise PolicyError('No policy with name %s exists.' % alias)
|
||||
if len(policy.alias_list) == 1:
|
||||
raise PolicyError('Policy %s with name %s has only one name. '
|
||||
'Policies must have at least one name.' % (
|
||||
policy, alias))
|
||||
else:
|
||||
policy.remove_name(alias)
|
||||
del self.by_name[alias.upper()]
|
||||
|
||||
def change_policy_primary_name(self, policy_index, new_name):
|
||||
"""
|
||||
Changes the primary or default name of a policy. The new primary
|
||||
name can be an alias that already belongs to the policy or a
|
||||
completely new name.
|
||||
|
||||
:param policy_index: index of a policy in this policy collection.
|
||||
:param new_name: a string name to set as the new default name.
|
||||
"""
|
||||
policy = self.get_by_index(policy_index)
|
||||
name_taken = self.get_by_name(new_name)
|
||||
# if the name belongs to some other policy in the collection
|
||||
if name_taken and name_taken != policy:
|
||||
raise PolicyError('Other policy %s with name %s exists.' %
|
||||
(self.get_by_name(new_name).idx, new_name))
|
||||
else:
|
||||
policy.change_primary_name(new_name)
|
||||
self.by_name[new_name.upper()] = policy
|
||||
|
||||
|
||||
def parse_storage_policies(conf):
|
||||
"""
|
||||
|
245
test/unit/common/test_storage_policy.py
Normal file → Executable file
245
test/unit/common/test_storage_policy.py
Normal file → Executable file
@ -17,7 +17,6 @@ import unittest
|
||||
import os
|
||||
import mock
|
||||
from functools import partial
|
||||
|
||||
from six.moves.configparser import ConfigParser
|
||||
from tempfile import NamedTemporaryFile
|
||||
from test.unit import patch_policies, FakeRing, temptree, DEFAULT_TEST_EC_TYPE
|
||||
@ -36,6 +35,7 @@ class FakeStoragePolicy(BaseStoragePolicy):
|
||||
Test StoragePolicy class - the only user at the moment is
|
||||
test_validate_policies_type_invalid()
|
||||
"""
|
||||
|
||||
def __init__(self, idx, name='', is_default=False, is_deprecated=False,
|
||||
object_ring=None):
|
||||
super(FakeStoragePolicy, self).__init__(
|
||||
@ -43,7 +43,6 @@ class FakeStoragePolicy(BaseStoragePolicy):
|
||||
|
||||
|
||||
class TestStoragePolicies(unittest.TestCase):
|
||||
|
||||
def _conf(self, conf_str):
|
||||
conf_str = "\n".join(line.strip() for line in conf_str.split("\n"))
|
||||
conf = ConfigParser()
|
||||
@ -75,10 +74,10 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
])
|
||||
def test_swift_info(self):
|
||||
# the deprecated 'three' should not exist in expect
|
||||
expect = [{'default': True, 'name': 'zero'},
|
||||
{'name': 'two'},
|
||||
{'name': 'one'},
|
||||
{'name': 'ten'}]
|
||||
expect = [{'aliases': 'zero', 'default': True, 'name': 'zero', },
|
||||
{'aliases': 'two', 'name': 'two'},
|
||||
{'aliases': 'one', 'name': 'one'},
|
||||
{'aliases': 'ten', 'name': 'ten'}]
|
||||
swift_info = POLICIES.get_policy_info()
|
||||
self.assertEqual(sorted(expect, key=lambda k: k['name']),
|
||||
sorted(swift_info, key=lambda k: k['name']))
|
||||
@ -286,6 +285,7 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
def test_validate_policies_type_invalid(self):
|
||||
class BogusStoragePolicy(FakeStoragePolicy):
|
||||
policy_type = 'bogus'
|
||||
|
||||
# unsupported policy type - initialization with FakeStoragePolicy
|
||||
self.assertRaisesWithMessage(PolicyError, 'Invalid type',
|
||||
BogusStoragePolicy, 1, 'one')
|
||||
@ -330,6 +330,221 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
self.assertEqual(pol1, policies.get_by_name(name))
|
||||
self.assertEqual(policies.get_by_name(name).name, 'One')
|
||||
|
||||
def test_multiple_names(self):
|
||||
# checking duplicate on insert
|
||||
test_policies = [StoragePolicy(0, 'zero', True),
|
||||
StoragePolicy(1, 'one', False, aliases='zero')]
|
||||
self.assertRaises(PolicyError, StoragePolicyCollection,
|
||||
test_policies)
|
||||
|
||||
# checking correct retrival using other names
|
||||
test_policies = [StoragePolicy(0, 'zero', True, aliases='cero, kore'),
|
||||
StoragePolicy(1, 'one', False, aliases='uno, tahi'),
|
||||
StoragePolicy(2, 'two', False, aliases='dos, rua')]
|
||||
|
||||
policies = StoragePolicyCollection(test_policies)
|
||||
|
||||
for name in ('zero', 'cero', 'kore'):
|
||||
self.assertEqual(policies.get_by_name(name), test_policies[0])
|
||||
for name in ('two', 'dos', 'rua'):
|
||||
self.assertEqual(policies.get_by_name(name), test_policies[2])
|
||||
|
||||
# Testing parsing of conf files/text
|
||||
good_conf = self._conf("""
|
||||
[storage-policy:0]
|
||||
name = one
|
||||
aliases = uno, tahi
|
||||
default = yes
|
||||
""")
|
||||
|
||||
policies = parse_storage_policies(good_conf)
|
||||
self.assertEqual(policies.get_by_name('one'),
|
||||
policies[0])
|
||||
self.assertEqual(policies.get_by_name('one'),
|
||||
policies.get_by_name('tahi'))
|
||||
|
||||
name_repeat_conf = self._conf("""
|
||||
[storage-policy:0]
|
||||
name = one
|
||||
aliases = one
|
||||
default = yes
|
||||
""")
|
||||
# Test on line below should not generate errors. Repeat of main
|
||||
# name under aliases is permitted during construction
|
||||
# but only because automated testing requires it.
|
||||
policies = parse_storage_policies(name_repeat_conf)
|
||||
|
||||
bad_conf = self._conf("""
|
||||
[storage-policy:0]
|
||||
name = one
|
||||
aliases = uno, uno
|
||||
default = yes
|
||||
""")
|
||||
|
||||
self.assertRaisesWithMessage(PolicyError,
|
||||
'is already assigned to this policy',
|
||||
parse_storage_policies, bad_conf)
|
||||
|
||||
def test_multiple_names_EC(self):
|
||||
# checking duplicate names on insert
|
||||
test_policies_ec = [
|
||||
ECStoragePolicy(
|
||||
0, 'ec8-2',
|
||||
aliases='zeus, jupiter',
|
||||
ec_type=DEFAULT_TEST_EC_TYPE,
|
||||
ec_ndata=8, ec_nparity=2,
|
||||
object_ring=FakeRing(replicas=8),
|
||||
is_default=True),
|
||||
ECStoragePolicy(
|
||||
1, 'ec10-4',
|
||||
aliases='ec8-2',
|
||||
ec_type=DEFAULT_TEST_EC_TYPE,
|
||||
ec_ndata=10, ec_nparity=4,
|
||||
object_ring=FakeRing(replicas=10))]
|
||||
|
||||
self.assertRaises(PolicyError, StoragePolicyCollection,
|
||||
test_policies_ec)
|
||||
|
||||
# checking correct retrival using other names
|
||||
good_test_policies_EC = [
|
||||
ECStoragePolicy(0, 'ec8-2', aliases='zeus, jupiter',
|
||||
ec_type=DEFAULT_TEST_EC_TYPE,
|
||||
ec_ndata=8, ec_nparity=2,
|
||||
object_ring=FakeRing(replicas=8),
|
||||
is_default=True),
|
||||
ECStoragePolicy(1, 'ec10-4', aliases='athena, minerva',
|
||||
ec_type=DEFAULT_TEST_EC_TYPE,
|
||||
ec_ndata=10, ec_nparity=4,
|
||||
object_ring=FakeRing(replicas=10)),
|
||||
ECStoragePolicy(2, 'ec4-2', aliases='poseidon, neptune',
|
||||
ec_type=DEFAULT_TEST_EC_TYPE,
|
||||
ec_ndata=4, ec_nparity=2,
|
||||
object_ring=FakeRing(replicas=7)),
|
||||
]
|
||||
ec_policies = StoragePolicyCollection(good_test_policies_EC)
|
||||
|
||||
for name in ('ec8-2', 'zeus', 'jupiter'):
|
||||
self.assertEqual(ec_policies.get_by_name(name), ec_policies[0])
|
||||
for name in ('ec10-4', 'athena', 'minerva'):
|
||||
self.assertEqual(ec_policies.get_by_name(name), ec_policies[1])
|
||||
|
||||
# Testing parsing of conf files/text
|
||||
good_ec_conf = self._conf("""
|
||||
[storage-policy:0]
|
||||
name = ec8-2
|
||||
aliases = zeus, jupiter
|
||||
policy_type = erasure_coding
|
||||
ec_type = %(ec_type)s
|
||||
default = yes
|
||||
ec_num_data_fragments = 8
|
||||
ec_num_parity_fragments = 2
|
||||
[storage-policy:1]
|
||||
name = ec10-4
|
||||
aliases = poseidon, neptune
|
||||
policy_type = erasure_coding
|
||||
ec_type = %(ec_type)s
|
||||
ec_num_data_fragments = 10
|
||||
ec_num_parity_fragments = 4
|
||||
""" % {'ec_type': DEFAULT_TEST_EC_TYPE})
|
||||
|
||||
ec_policies = parse_storage_policies(good_ec_conf)
|
||||
self.assertEqual(ec_policies.get_by_name('ec8-2'),
|
||||
ec_policies[0])
|
||||
self.assertEqual(ec_policies.get_by_name('ec10-4'),
|
||||
ec_policies.get_by_name('poseidon'))
|
||||
|
||||
name_repeat_ec_conf = self._conf("""
|
||||
[storage-policy:0]
|
||||
name = ec8-2
|
||||
aliases = ec8-2
|
||||
policy_type = erasure_coding
|
||||
ec_type = %(ec_type)s
|
||||
default = yes
|
||||
ec_num_data_fragments = 8
|
||||
ec_num_parity_fragments = 2
|
||||
""" % {'ec_type': DEFAULT_TEST_EC_TYPE})
|
||||
# Test on line below should not generate errors. Repeat of main
|
||||
# name under aliases is permitted during construction
|
||||
# but only because automated testing requires it.
|
||||
ec_policies = parse_storage_policies(name_repeat_ec_conf)
|
||||
|
||||
bad_ec_conf = self._conf("""
|
||||
[storage-policy:0]
|
||||
name = ec8-2
|
||||
aliases = zeus, zeus
|
||||
policy_type = erasure_coding
|
||||
ec_type = %(ec_type)s
|
||||
default = yes
|
||||
ec_num_data_fragments = 8
|
||||
ec_num_parity_fragments = 2
|
||||
""" % {'ec_type': DEFAULT_TEST_EC_TYPE})
|
||||
self.assertRaisesWithMessage(PolicyError,
|
||||
'is already assigned to this policy',
|
||||
parse_storage_policies, bad_ec_conf)
|
||||
|
||||
def test_add_remove_names(self):
|
||||
test_policies = [StoragePolicy(0, 'zero', True),
|
||||
StoragePolicy(1, 'one', False),
|
||||
StoragePolicy(2, 'two', False)]
|
||||
policies = StoragePolicyCollection(test_policies)
|
||||
|
||||
# add names
|
||||
policies.add_policy_alias(1, 'tahi')
|
||||
self.assertEqual(policies.get_by_name('tahi'), test_policies[1])
|
||||
|
||||
policies.add_policy_alias(2, 'rua', 'dos')
|
||||
self.assertEqual(policies.get_by_name('rua'), test_policies[2])
|
||||
self.assertEqual(policies.get_by_name('dos'), test_policies[2])
|
||||
|
||||
self.assertRaisesWithMessage(PolicyError, 'Invalid name',
|
||||
policies.add_policy_alias, 2, 'double\n')
|
||||
|
||||
# try to add existing name
|
||||
self.assertRaisesWithMessage(PolicyError, 'Duplicate name',
|
||||
policies.add_policy_alias, 2, 'two')
|
||||
|
||||
self.assertRaisesWithMessage(PolicyError, 'Duplicate name',
|
||||
policies.add_policy_alias, 1, 'two')
|
||||
|
||||
# remove name
|
||||
policies.remove_policy_alias('tahi')
|
||||
self.assertEqual(policies.get_by_name('tahi'), None)
|
||||
|
||||
# remove only name
|
||||
self.assertRaisesWithMessage(PolicyError,
|
||||
'Policies must have at least one name.',
|
||||
policies.remove_policy_alias, 'zero')
|
||||
|
||||
# remove non-existent name
|
||||
self.assertRaisesWithMessage(PolicyError,
|
||||
'No policy with name',
|
||||
policies.remove_policy_alias, 'three')
|
||||
|
||||
# remove default name
|
||||
policies.remove_policy_alias('two')
|
||||
self.assertEqual(policies.get_by_name('two'), None)
|
||||
self.assertEqual(policies.get_by_index(2).name, 'rua')
|
||||
|
||||
# change default name to a new name
|
||||
policies.change_policy_primary_name(2, 'two')
|
||||
self.assertEqual(policies.get_by_name('two'), test_policies[2])
|
||||
self.assertEqual(policies.get_by_index(2).name, 'two')
|
||||
|
||||
# change default name to an existing alias
|
||||
policies.change_policy_primary_name(2, 'dos')
|
||||
self.assertEqual(policies.get_by_index(2).name, 'dos')
|
||||
|
||||
# change default name to a bad new name
|
||||
self.assertRaisesWithMessage(PolicyError, 'Invalid name',
|
||||
policies.change_policy_primary_name,
|
||||
2, 'bad\nname')
|
||||
|
||||
# change default name to a name belonging to another policy
|
||||
self.assertRaisesWithMessage(PolicyError,
|
||||
'Other policy',
|
||||
policies.change_policy_primary_name,
|
||||
1, 'dos')
|
||||
|
||||
def test_deprecated_default(self):
|
||||
bad_conf = self._conf("""
|
||||
[storage-policy:1]
|
||||
@ -815,7 +1030,7 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
part_shift=24)
|
||||
|
||||
with mock.patch(
|
||||
'swift.common.storage_policy.RingData.load'
|
||||
'swift.common.storage_policy.RingData.load'
|
||||
) as mock_ld, \
|
||||
patch_policies(test_policies), \
|
||||
mock.patch('swift.common.storage_policy.whataremyips') \
|
||||
@ -933,14 +1148,14 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
msg = 'EC ring for policy %s needs to be configured with ' \
|
||||
'exactly %d nodes.' % \
|
||||
(policy.name, policy.ec_ndata + policy.ec_nparity)
|
||||
self.assertRaisesWithMessage(
|
||||
RingValidationError, msg,
|
||||
policy._validate_ring)
|
||||
self.assertRaisesWithMessage(RingValidationError, msg,
|
||||
policy._validate_ring)
|
||||
|
||||
def test_storage_policy_get_info(self):
|
||||
test_policies = [
|
||||
StoragePolicy(0, 'zero', is_default=True),
|
||||
StoragePolicy(1, 'one', is_deprecated=True),
|
||||
StoragePolicy(1, 'one', is_deprecated=True,
|
||||
aliases='tahi, uno'),
|
||||
ECStoragePolicy(10, 'ten',
|
||||
ec_type=DEFAULT_TEST_EC_TYPE,
|
||||
ec_ndata=10, ec_nparity=3),
|
||||
@ -953,28 +1168,33 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
# default replication
|
||||
(0, True): {
|
||||
'name': 'zero',
|
||||
'aliases': 'zero',
|
||||
'default': True,
|
||||
'deprecated': False,
|
||||
'policy_type': REPL_POLICY
|
||||
},
|
||||
(0, False): {
|
||||
'name': 'zero',
|
||||
'aliases': 'zero',
|
||||
'default': True,
|
||||
},
|
||||
# deprecated replication
|
||||
(1, True): {
|
||||
'name': 'one',
|
||||
'aliases': 'one, tahi, uno',
|
||||
'default': False,
|
||||
'deprecated': True,
|
||||
'policy_type': REPL_POLICY
|
||||
},
|
||||
(1, False): {
|
||||
'name': 'one',
|
||||
'aliases': 'one, tahi, uno',
|
||||
'deprecated': True,
|
||||
},
|
||||
# enabled ec
|
||||
(10, True): {
|
||||
'name': 'ten',
|
||||
'aliases': 'ten',
|
||||
'default': False,
|
||||
'deprecated': False,
|
||||
'policy_type': EC_POLICY,
|
||||
@ -985,10 +1205,12 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
},
|
||||
(10, False): {
|
||||
'name': 'ten',
|
||||
'aliases': 'ten',
|
||||
},
|
||||
# deprecated ec
|
||||
(11, True): {
|
||||
'name': 'done',
|
||||
'aliases': 'done',
|
||||
'default': False,
|
||||
'deprecated': True,
|
||||
'policy_type': EC_POLICY,
|
||||
@ -999,6 +1221,7 @@ class TestStoragePolicies(unittest.TestCase):
|
||||
},
|
||||
(11, False): {
|
||||
'name': 'done',
|
||||
'aliases': 'done',
|
||||
'deprecated': True,
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user