Added attribute filter to scheduler

Change-Id: I80ce5621c75f05b794a4093696cf118a35a702ea
This commit is contained in:
Graham Hayes 2016-04-15 17:21:24 +01:00
parent a80a704ef4
commit ae8532248c
5 changed files with 284 additions and 9 deletions

View File

@ -11,15 +11,18 @@
# 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 six
from oslo_log import log as logging
from oslo_utils.strutils import bool_from_string
from designate.objects import PoolAttributeList
from designate.scheduler.filters.base import Filter
LOG = logging.getLogger(__name__)
class AttributeFilter(Filter):
"""This allows users top choose the pool by supplying hints to this filter.
"""This allows users to choose the pool by supplying hints to this filter.
These are provided as attributes as part of the zone object provided at
zone create time.
@ -29,7 +32,7 @@ class AttributeFilter(Filter):
{
"attributes": {
"pool_level": "gold",
"fast_ttl": True,
"fast_ttl": "true",
"pops": "global",
},
"email": "user@example.com",
@ -39,11 +42,6 @@ class AttributeFilter(Filter):
The zone attributes are matched against the potential pool candiates, and
any pools that do not match **all** hints are removed.
.. warning::
This filter is disabled currently, and should not be used.
It will be enabled at a later date.
.. warning::
This should be uses in conjunction with the
@ -58,5 +56,57 @@ class AttributeFilter(Filter):
"""
def filter(self, context, pools, zone):
# FIXME (graham) actually filter on attributes
zone_attributes = zone.attributes.to_dict()
def evaluate_pool(pool):
pool_attributes = pool.attributes.to_dict()
# Check if the keys requested exist in this pool
if not {key for key in six.iterkeys(pool_attributes)}.issuperset(
zone_attributes):
msg = "%(pool)s did not match list of requested attribute "\
"keys - removing from list. Requested: %(r_key)s. Pool:"\
"%(p_key)s"
LOG.debug(
msg,
{
'pool': pool,
'r_key': zone_attributes,
'p_key': pool_attributes
}
)
# Missing required keys - remove from the list
return False
for key in six.iterkeys(zone_attributes):
LOG.debug("Checking value of %s for %s", key, pool)
pool_attr = bool_from_string(pool_attributes[key],
default=pool_attributes[key])
zone_attr = bool_from_string(zone_attributes[key],
default=zone_attributes[key])
if not pool_attr == zone_attr:
LOG.debug(
"%(pool)s did not match requested value of %(key)s. "
"Requested: %(r_val)s. Pool: %(p_val)s",
{
'pool': pool,
'key': key,
'r_val': zone_attr,
'p_val': pool_attr
})
# Value didn't match - remove from the list
return False
# Pool matches list of attributes - keep
return True
pool_list = [pool for pool in pools if evaluate_pool(pool)]
pools = PoolAttributeList(objects=pool_list)
return pools

View File

@ -67,7 +67,7 @@ class HeartBeatEmitter(plugin.DriverPlugin):
if not self._running:
return
LOG.debug("Emitting heartbeat...")
LOG.trace("Emitting heartbeat...")
status, stats, capabilities = self._get_status()

View File

@ -22,6 +22,7 @@ from oslotest import base as test
from designate.scheduler.filters import default_pool_filter
from designate.scheduler.filters import fallback_filter
from designate.scheduler.filters import pool_id_attribute_filter
from designate.scheduler.filters import attribute_filter
from designate import objects
from designate import context
from designate import policy
@ -202,3 +203,221 @@ class SchedulerPoolIDAttributeFilterTest(SchedulerFilterTest):
'zone_create_forced_pool',
self.context,
pools[0])
class SchedulerAttributeFilterTest(SchedulerFilterTest):
FILTER = attribute_filter.AttributeFilter
def setUp(self):
super(SchedulerAttributeFilterTest, self).setUp()
self.zone = objects.Zone(
name="example.com.",
type="PRIMARY",
email="hostmaster@example.com",
attributes=objects.ZoneAttributeList.from_list(
[
{
"key": "attribute_one",
"value": "True"
},
{
"key": "attribute_two",
"value": "False"
},
{
"key": "attribute_three",
"value": "foo"
}
]
)
)
def test_default_operation(self):
pools = objects.PoolList.from_list(
[
{
"id": "6c346011-e581-429b-a7a2-6cdf0aba91c3",
}
]
)
pools[0].attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_one",
"value": "True"
},
{
"key": "attribute_two",
"value": "False"
},
{
"key": "attribute_three",
"value": "foo"
}
])
pools = self.test_filter.filter(self.context, pools, self.zone)
self.assertEqual("6c346011-e581-429b-a7a2-6cdf0aba91c3", pools[0].id)
def test_multiple_pools_all_match(self):
pools = objects.PoolList.from_list(
[
{"id": "6c346011-e581-429b-a7a2-6cdf0aba91c3"},
{"id": "5fabcd37-262c-4cf3-8625-7f419434b6df"}
]
)
attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_one",
"value": "True"
},
{
"key": "attribute_two",
"value": "False"
},
{
"key": "attribute_three",
"value": "foo"
}
])
pools[0].attributes = attributes
pools[1].attributes = attributes
pools = self.test_filter.filter(self.context, pools, self.zone)
self.assertEqual(2, len(pools))
def test_multiple_pools_one_match(self):
pools = objects.PoolList.from_list(
[
{"id": "6c346011-e581-429b-a7a2-6cdf0aba91c3"},
{"id": "5fabcd37-262c-4cf3-8625-7f419434b6df"}
]
)
pool_0_attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_one",
"value": "True"
},
{
"key": "attribute_two",
"value": "False"
},
{
"key": "attribute_three",
"value": "foo"
}
])
pool_1_attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_four",
"value": "True"
},
{
"key": "attribute_five",
"value": "False"
},
{
"key": "attribute_three",
"value": "foo"
}
])
pools[0].attributes = pool_0_attributes
pools[1].attributes = pool_1_attributes
pools = self.test_filter.filter(self.context, pools, self.zone)
self.assertEqual(1, len(pools))
self.assertEqual("6c346011-e581-429b-a7a2-6cdf0aba91c3", pools[0].id)
def test_multiple_pools_no_match(self):
pools = objects.PoolList.from_list(
[
{"id": "6c346011-e581-429b-a7a2-6cdf0aba91c3"},
{"id": "5fabcd37-262c-4cf3-8625-7f419434b6df"}
]
)
pool_0_attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_six",
"value": "True"
},
{
"key": "attribute_two",
"value": "False"
},
{
"key": "attribute_seven",
"value": "foo"
}
])
pool_1_attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_four",
"value": "True"
},
{
"key": "attribute_five",
"value": "False"
},
{
"key": "attribute_three",
"value": "foo"
}
])
pools[0].attributes = pool_0_attributes
pools[1].attributes = pool_1_attributes
pools = self.test_filter.filter(self.context, pools, self.zone)
self.assertEqual(0, len(pools))
def test_no_match_non_bool(self):
pools = objects.PoolList.from_list(
[
{"id": "6c346011-e581-429b-a7a2-6cdf0aba91c3"},
]
)
pool_0_attributes = objects.PoolAttributeList.from_list(
[
{
"key": "attribute_one",
"value": "True"
},
{
"key": "attribute_two",
"value": "False"
},
{
"key": "attribute_three",
"value": "bar"
}
])
pools[0].attributes = pool_0_attributes
pools = self.test_filter.filter(self.context, pools, self.zone)
self.assertEqual(0, len(pools))

View File

@ -0,0 +1,5 @@
---
features:
- Addition of the "attribute" filter for scheduling zones across pools.
This can be enabled in the ``[service:central]`` section of the config
by adding ``attribute`` to the list of values in the ``filters`` option.

View File

@ -104,6 +104,7 @@ designate.quota =
designate.scheduler.filters =
fallback = designate.scheduler.filters.fallback_filter:FallbackFilter
attribute = designate.scheduler.filters.attribute_filter:AttributeFilter
random = designate.scheduler.filters.random_filter:RandomFilter
pool_id_attribute = designate.scheduler.filters.pool_id_attribute_filter:PoolIDAttributeFilter
default_pool = designate.scheduler.filters.default_pool_filter:DefaultPoolFilter