1686 lines
70 KiB
Python
1686 lines
70 KiB
Python
# Copyright 2011 OpenStack Foundation # All Rights Reserved.
|
|
#
|
|
# 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.
|
|
"""
|
|
Tests For Scheduler Host Filters.
|
|
"""
|
|
|
|
import mock
|
|
from oslo_serialization import jsonutils
|
|
from requests import exceptions as request_exceptions
|
|
|
|
from cinder.compute import nova
|
|
from cinder import context
|
|
from cinder import db
|
|
from cinder import exception
|
|
from cinder.scheduler import filters
|
|
from cinder.scheduler.filters import extra_specs_ops
|
|
from cinder import test
|
|
from cinder.tests.unit import fake_constants as fake
|
|
from cinder.tests.unit.scheduler import fakes
|
|
from cinder.tests.unit import utils
|
|
|
|
|
|
class HostFiltersTestCase(test.TestCase):
|
|
"""Test case for host filters."""
|
|
|
|
def setUp(self):
|
|
super(HostFiltersTestCase, self).setUp()
|
|
self.context = context.RequestContext(fake.user_id, fake.project_id)
|
|
# This has a side effect of testing 'get_filter_classes'
|
|
# when specifying a method (in this case, our standard filters)
|
|
filter_handler = filters.HostFilterHandler('cinder.scheduler.filters')
|
|
classes = filter_handler.get_all_classes()
|
|
self.class_map = {}
|
|
for cls in classes:
|
|
self.class_map[cls.__name__] = cls
|
|
|
|
|
|
class CapacityFilterTestCase(HostFiltersTestCase):
|
|
def setUp(self):
|
|
super(CapacityFilterTestCase, self).setUp()
|
|
self.json_query = jsonutils.dumps(
|
|
['and',
|
|
['>=', '$free_capacity_gb', 1024],
|
|
['>=', '$total_capacity_gb', 10 * 1024]])
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 200,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_current_host_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100, 'vol_exists_on': 'host1'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 100,
|
|
'free_capacity_gb': 10,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_fails(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 200,
|
|
'free_capacity_gb': 120,
|
|
'reserved_percentage': 20,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_fails_free_capacity_None(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_capacity_gb': None,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_passes_infinite(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_capacity_gb': 'infinite',
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_passes_unknown(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_capacity_gb': 'unknown',
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_passes_total_infinite(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_capacity_gb': 'infinite',
|
|
'total_capacity_gb': 'infinite',
|
|
'reserved_percentage': 0,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_passes_total_unknown(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_capacity_gb': 'unknown',
|
|
'total_capacity_gb': 'unknown',
|
|
'reserved_percentage': 0,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_fails_total_infinite(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 'infinite',
|
|
'reserved_percentage': 5,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_fails_total_unknown(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 'unknown',
|
|
'reserved_percentage': 5,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_fails_total_zero(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 0,
|
|
'reserved_percentage': 5,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_thin_true_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 200,
|
|
'provisioned_capacity_gb': 500,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_thin_true_passes2(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 3000,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 200,
|
|
'provisioned_capacity_gb': 7000,
|
|
'max_over_subscription_ratio': 20,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_thin_false_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> False',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> True'}
|
|
service = {'disabled': False}
|
|
# If "thin_provisioning_support" is False,
|
|
# "max_over_subscription_ratio" will be ignored.
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 200,
|
|
'provisioned_capacity_gb': 300,
|
|
'max_over_subscription_ratio': 1.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': False,
|
|
'thick_provisioning_support': True,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_over_subscription_less_than_1(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 200,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 100,
|
|
'provisioned_capacity_gb': 400,
|
|
'max_over_subscription_ratio': 0.8,
|
|
'reserved_percentage': 0,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_over_subscription_equal_to_1(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 150,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 200,
|
|
'provisioned_capacity_gb': 400,
|
|
'max_over_subscription_ratio': 1.0,
|
|
'reserved_percentage': 0,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_over_subscription_fails(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 200,
|
|
'provisioned_capacity_gb': 700,
|
|
'max_over_subscription_ratio': 1.5,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_over_subscription_fails2(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 2000,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 30,
|
|
'provisioned_capacity_gb': 9000,
|
|
'max_over_subscription_ratio': 20,
|
|
'reserved_percentage': 0,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_true_fails(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 100,
|
|
'provisioned_capacity_gb': 1000,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_false_fails(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> False',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> True'}
|
|
service = {'disabled': False}
|
|
# If "thin_provisioning_support" is False,
|
|
# "max_over_subscription_ratio" will be ignored.
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 100,
|
|
'provisioned_capacity_gb': 400,
|
|
'max_over_subscription_ratio': 1.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': False,
|
|
'thick_provisioning_support': True,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_thick_true_fails(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> True'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 0,
|
|
'provisioned_capacity_gb': 800,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': True,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_thick_true_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> True'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 125,
|
|
'provisioned_capacity_gb': 400,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': True,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_true_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> False'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 80,
|
|
'provisioned_capacity_gb': 600,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': False,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_thick_true_fails2(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> True'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 99,
|
|
'provisioned_capacity_gb': 1000,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 5,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': True,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_filter_reserved_thin_thick_true_passes2(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['CapacityFilter']()
|
|
filter_properties = {'size': 100,
|
|
'capabilities:thin_provisioning_support':
|
|
'<is> True',
|
|
'capabilities:thick_provisioning_support':
|
|
'<is> True'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'total_capacity_gb': 500,
|
|
'free_capacity_gb': 100,
|
|
'provisioned_capacity_gb': 400,
|
|
'max_over_subscription_ratio': 2.0,
|
|
'reserved_percentage': 0,
|
|
'thin_provisioning_support': True,
|
|
'thick_provisioning_support': True,
|
|
'updated_at': None,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
|
|
class AffinityFilterTestCase(HostFiltersTestCase):
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_different_filter_passes(self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1:pool0',
|
|
{'free_capacity_gb': '1000',
|
|
'updated_at': None,
|
|
'service': service})
|
|
volume = utils.create_volume(self.context, host='host1:pool1')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': [vol_id], }}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('cinder.utils.service_is_up')
|
|
def test_different_filter_legacy_volume_hint_passes(
|
|
self, _mock_serv_is_up):
|
|
_mock_serv_is_up.return_value = True
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1:pool0',
|
|
{'free_capacity_gb': '1000',
|
|
'updated_at': None,
|
|
'service': service})
|
|
volume = utils.create_volume(self.context, host='host1')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': [vol_id], }}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_non_list_fails(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host2', {})
|
|
volume = utils.create_volume(self.context, host='host2')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': vol_id}}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_fails(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
volume = utils.create_volume(self.context, host='host1')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': [vol_id], }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_handles_none(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': None}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_handles_deleted_instance(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
volume = utils.create_volume(self.context, host='host1')
|
|
vol_id = volume.id
|
|
db.volume_destroy(utils.get_test_admin_context(), vol_id)
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': [vol_id], }}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_fail_nonuuid_hint(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': "NOT-a-valid-UUID", }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_handles_multiple_uuids(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host1#pool0', {})
|
|
volume1 = utils.create_volume(self.context, host='host1:pool1')
|
|
vol_id1 = volume1.id
|
|
volume2 = utils.create_volume(self.context, host='host1:pool3')
|
|
vol_id2 = volume2.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': [vol_id1, vol_id2], }}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_different_filter_handles_invalid_uuids(self):
|
|
filt_cls = self.class_map['DifferentBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
volume = utils.create_volume(self.context, host='host2')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'different_host': [vol_id, "NOT-a-valid-UUID"], }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_no_list_passes(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
volume = utils.create_volume(self.context, host='host1')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': vol_id}}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_passes(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1#pool0', {})
|
|
volume = utils.create_volume(self.context, host='host1#pool0')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': [vol_id], }}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_legacy_vol_fails(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1#pool0', {})
|
|
volume = utils.create_volume(self.context, host='host1')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': [vol_id], }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_fails(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1#pool0', {})
|
|
volume = utils.create_volume(self.context, host='host1#pool1')
|
|
vol_id = volume.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': [vol_id], }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_vol_list_pass(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
volume1 = utils.create_volume(self.context, host='host1')
|
|
vol_id1 = volume1.id
|
|
volume2 = utils.create_volume(self.context, host='host2')
|
|
vol_id2 = volume2.id
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': [vol_id1, vol_id2], }}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_handles_none(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': None}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_handles_deleted_instance(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
volume = utils.create_volume(self.context, host='host2')
|
|
vol_id = volume.id
|
|
db.volume_destroy(utils.get_test_admin_context(), vol_id)
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': [vol_id], }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_same_filter_fail_nonuuid_hint(self):
|
|
filt_cls = self.class_map['SameBackendFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context.elevated(),
|
|
'scheduler_hints': {
|
|
'same_host': "NOT-a-valid-UUID", }}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
|
|
class DriverFilterTestCase(HostFiltersTestCase):
|
|
def test_passing_function(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': '1 == 1',
|
|
}
|
|
})
|
|
|
|
filter_properties = {'volume_type': {}}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_failing_function(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': '1 == 2',
|
|
}
|
|
})
|
|
|
|
filter_properties = {'volume_type': {}}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_no_filter_function(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': None,
|
|
}
|
|
})
|
|
|
|
filter_properties = {'volume_type': {}}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_not_implemented(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {}
|
|
})
|
|
|
|
filter_properties = {'volume_type': {}}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_no_volume_extra_specs(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': '1 == 1',
|
|
}
|
|
})
|
|
|
|
filter_properties = {'volume_type': {}}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_function_extra_spec_replacement(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': 'extra.var == 1',
|
|
}
|
|
})
|
|
|
|
filter_properties = {
|
|
'volume_type': {
|
|
'extra_specs': {
|
|
'var': 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_function_stats_replacement(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'total_capacity_gb': 100,
|
|
'capabilities': {
|
|
'filter_function': 'stats.total_capacity_gb < 200',
|
|
}
|
|
})
|
|
|
|
filter_properties = {'volume_type': {}}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_function_volume_replacement(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': 'volume.size < 5',
|
|
}
|
|
})
|
|
|
|
filter_properties = {
|
|
'request_spec': {
|
|
'volume_properties': {
|
|
'size': 1
|
|
}
|
|
}
|
|
}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_function_qos_spec_replacement(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': 'qos.var == 1',
|
|
}
|
|
})
|
|
|
|
filter_properties = {
|
|
'qos_specs': {
|
|
'var': 1
|
|
}
|
|
}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_function_exception_caught(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': '1 / 0 == 0',
|
|
}
|
|
})
|
|
|
|
filter_properties = {}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_function_empty_qos(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'filter_function': 'qos.maxiops == 1',
|
|
}
|
|
})
|
|
|
|
filter_properties = {
|
|
'qos_specs': None
|
|
}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_capabilities(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'foo': 10,
|
|
'filter_function': 'capabilities.foo == 10',
|
|
},
|
|
})
|
|
|
|
filter_properties = {}
|
|
|
|
self.assertTrue(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
def test_wrong_capabilities(self):
|
|
filt_cls = self.class_map['DriverFilter']()
|
|
host1 = fakes.FakeHostState(
|
|
'host1', {
|
|
'capabilities': {
|
|
'bar': 10,
|
|
'filter_function': 'capabilities.foo == 10',
|
|
},
|
|
})
|
|
|
|
filter_properties = {}
|
|
|
|
self.assertFalse(filt_cls.host_passes(host1, filter_properties))
|
|
|
|
|
|
class InstanceLocalityFilterTestCase(HostFiltersTestCase):
|
|
def setUp(self):
|
|
super(InstanceLocalityFilterTestCase, self).setUp()
|
|
self.override_config('nova_endpoint_template',
|
|
'http://novahost:8774/v2/%(project_id)s')
|
|
self.context.service_catalog = \
|
|
[{'type': 'compute', 'name': 'nova', 'endpoints':
|
|
[{'publicURL': 'http://novahost:8774/v2/e3f0833dc08b4cea'}]},
|
|
{'type': 'identity', 'name': 'keystone', 'endpoints':
|
|
[{'publicURL': 'http://keystonehost:5000/v2.0'}]}]
|
|
|
|
@mock.patch('novaclient.client.discover_extensions')
|
|
@mock.patch('cinder.compute.nova.novaclient')
|
|
def test_same_host(self, _mock_novaclient, fake_extensions):
|
|
_mock_novaclient.return_value = fakes.FakeNovaClient()
|
|
fake_extensions.return_value = (
|
|
fakes.FakeNovaClient().list_extensions.show_all())
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
uuid = nova.novaclient().servers.create('host1')
|
|
|
|
filter_properties = {'context': self.context,
|
|
'scheduler_hints': {'local_to_instance': uuid}}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('novaclient.client.discover_extensions')
|
|
@mock.patch('cinder.compute.nova.novaclient')
|
|
def test_different_host(self, _mock_novaclient, fake_extensions):
|
|
_mock_novaclient.return_value = fakes.FakeNovaClient()
|
|
fake_extensions.return_value = (
|
|
fakes.FakeNovaClient().list_extensions.show_all())
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
uuid = nova.novaclient().servers.create('host2')
|
|
|
|
filter_properties = {'context': self.context,
|
|
'scheduler_hints': {'local_to_instance': uuid}}
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_handles_none(self):
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context,
|
|
'scheduler_hints': None}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_invalid_uuid(self):
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context,
|
|
'scheduler_hints':
|
|
{'local_to_instance': 'e29b11d4-not-valid-a716'}}
|
|
self.assertRaises(exception.InvalidUUID,
|
|
filt_cls.host_passes, host, filter_properties)
|
|
|
|
@mock.patch('cinder.compute.nova.novaclient')
|
|
def test_nova_no_extended_server_attributes(self, _mock_novaclient):
|
|
_mock_novaclient.return_value = fakes.FakeNovaClient(
|
|
ext_srv_attr=False)
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
uuid = nova.novaclient().servers.create('host1')
|
|
|
|
filter_properties = {'context': self.context,
|
|
'scheduler_hints': {'local_to_instance': uuid}}
|
|
self.assertRaises(exception.CinderException,
|
|
filt_cls.host_passes, host, filter_properties)
|
|
|
|
@mock.patch('cinder.compute.nova.novaclient')
|
|
def test_nova_down_does_not_alter_other_filters(self, _mock_novaclient):
|
|
# Simulate Nova API is not available
|
|
_mock_novaclient.side_effect = Exception
|
|
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = {'context': self.context, 'size': 100}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@mock.patch('novaclient.client.discover_extensions')
|
|
@mock.patch('requests.request')
|
|
def test_nova_timeout(self, _mock_request, fake_extensions):
|
|
# Simulate a HTTP timeout
|
|
_mock_request.side_effect = request_exceptions.Timeout
|
|
fake_extensions.return_value = (
|
|
fakes.FakeNovaClient().list_extensions.show_all())
|
|
|
|
filt_cls = self.class_map['InstanceLocalityFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
|
|
filter_properties = \
|
|
{'context': self.context, 'scheduler_hints':
|
|
{'local_to_instance': 'e29b11d4-15ef-34a9-a716-598a6f0b5467'}}
|
|
self.assertRaises(exception.APITimeout,
|
|
filt_cls.host_passes, host, filter_properties)
|
|
|
|
|
|
class TestFilter(filters.BaseHostFilter):
|
|
pass
|
|
|
|
|
|
class TestBogusFilter(object):
|
|
"""Class that doesn't inherit from BaseHostFilter."""
|
|
pass
|
|
|
|
|
|
class ExtraSpecsOpsTestCase(test.TestCase):
|
|
def _do_extra_specs_ops_test(self, value, req, matches):
|
|
assertion = self.assertTrue if matches else self.assertFalse
|
|
assertion(extra_specs_ops.match(value, req))
|
|
|
|
def test_extra_specs_matches_simple(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='1',
|
|
req='1',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_simple(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='',
|
|
req='1',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_simple2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='3',
|
|
req='1',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_simple3(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='222',
|
|
req='2',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_bogus_ops(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='4',
|
|
req='> 2',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_eq(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='123',
|
|
req='= 123',
|
|
matches=True)
|
|
|
|
def test_extra_specs_matches_with_op_eq2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='124',
|
|
req='= 123',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_eq(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='34',
|
|
req='= 234',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_eq3(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='34',
|
|
req='=',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_seq(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='123',
|
|
req='s== 123',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_seq(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='1234',
|
|
req='s== 123',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_sneq(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='1234',
|
|
req='s!= 123',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_sneq(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='123',
|
|
req='s!= 123',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_sge(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='1000',
|
|
req='s>= 234',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_sle(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='1234',
|
|
req='s<= 1000',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_sl(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='2',
|
|
req='s< 12',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_sg(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12',
|
|
req='s> 2',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_in(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12311321',
|
|
req='<in> 11',
|
|
matches=True)
|
|
|
|
def test_extra_specs_matches_with_op_in2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12311321',
|
|
req='<in> 12311321',
|
|
matches=True)
|
|
|
|
def test_extra_specs_matches_with_op_in3(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12311321',
|
|
req='<in> 12311321 <in>',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_in(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12310321',
|
|
req='<in> 11',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_in2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12310321',
|
|
req='<in> 11 <in>',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_is(self):
|
|
self._do_extra_specs_ops_test(
|
|
value=True,
|
|
req='<is> True',
|
|
matches=True)
|
|
|
|
def test_extra_specs_matches_with_op_is2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value=False,
|
|
req='<is> False',
|
|
matches=True)
|
|
|
|
def test_extra_specs_matches_with_op_is3(self):
|
|
self._do_extra_specs_ops_test(
|
|
value=False,
|
|
req='<is> Nonsense',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_is(self):
|
|
self._do_extra_specs_ops_test(
|
|
value=True,
|
|
req='<is> False',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_is2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value=False,
|
|
req='<is> True',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_or(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12',
|
|
req='<or> 11 <or> 12',
|
|
matches=True)
|
|
|
|
def test_extra_specs_matches_with_op_or2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='12',
|
|
req='<or> 11 <or> 12 <or>',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_or(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='13',
|
|
req='<or> 11 <or> 12',
|
|
matches=False)
|
|
|
|
def test_extra_specs_fails_with_op_or2(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='13',
|
|
req='<or> 11 <or> 12 <or>',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_le(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='2',
|
|
req='<= 10',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_le(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='3',
|
|
req='<= 2',
|
|
matches=False)
|
|
|
|
def test_extra_specs_matches_with_op_ge(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='3',
|
|
req='>= 1',
|
|
matches=True)
|
|
|
|
def test_extra_specs_fails_with_op_ge(self):
|
|
self._do_extra_specs_ops_test(
|
|
value='2',
|
|
req='>= 3',
|
|
matches=False)
|
|
|
|
|
|
class BasicFiltersTestCase(HostFiltersTestCase):
|
|
"""Test case for host filters."""
|
|
|
|
def setUp(self):
|
|
super(BasicFiltersTestCase, self).setUp()
|
|
self.json_query = jsonutils.dumps(
|
|
['and', ['>=', '$free_ram_mb', 1024],
|
|
['>=', '$free_disk_mb', 200 * 1024]])
|
|
|
|
def test_all_filters(self):
|
|
# Double check at least a couple of known filters exist
|
|
self.assertTrue('JsonFilter' in self.class_map)
|
|
self.assertTrue('CapabilitiesFilter' in self.class_map)
|
|
self.assertTrue('AvailabilityZoneFilter' in self.class_map)
|
|
self.assertTrue('IgnoreAttemptedHostsFilter' in self.class_map)
|
|
|
|
def _do_test_type_filter_extra_specs(self, ecaps, especs, passes):
|
|
filt_cls = self.class_map['CapabilitiesFilter']()
|
|
capabilities = {'enabled': True}
|
|
capabilities.update(ecaps)
|
|
service = {'disabled': False}
|
|
filter_properties = {'resource_type': {'name': 'fake_type',
|
|
'extra_specs': especs}}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_capacity_gb': 1024,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
assertion = self.assertTrue if passes else self.assertFalse
|
|
assertion(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_capability_filter_passes_extra_specs_simple(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'opt1': '1', 'opt2': '2'},
|
|
especs={'opt1': '1', 'opt2': '2'},
|
|
passes=True)
|
|
|
|
def test_capability_filter_fails_extra_specs_simple(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'opt1': '1', 'opt2': '2'},
|
|
especs={'opt1': '1', 'opt2': '222'},
|
|
passes=False)
|
|
|
|
def test_capability_filter_passes_extra_specs_complex(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'opt1': 10, 'opt2': 5},
|
|
especs={'opt1': '>= 2', 'opt2': '<= 8'},
|
|
passes=True)
|
|
|
|
def test_capability_filter_fails_extra_specs_complex(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'opt1': 10, 'opt2': 5},
|
|
especs={'opt1': '>= 2', 'opt2': '>= 8'},
|
|
passes=False)
|
|
|
|
def test_capability_filter_passes_scope_extra_specs(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'scope_lv1': {'opt1': 10}},
|
|
especs={'capabilities:scope_lv1:opt1': '>= 2'},
|
|
passes=True)
|
|
|
|
def test_capability_filter_passes_fakescope_extra_specs(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'scope_lv1': {'opt1': 10}, 'opt2': 5},
|
|
especs={'scope_lv1:opt1': '= 2', 'opt2': '>= 3'},
|
|
passes=True)
|
|
|
|
def test_capability_filter_fails_scope_extra_specs(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'scope_lv1': {'opt1': 10}},
|
|
especs={'capabilities:scope_lv1:opt1': '<= 2'},
|
|
passes=False)
|
|
|
|
def test_capability_filter_passes_multi_level_scope_extra_specs(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'scope_lv0': {'scope_lv1':
|
|
{'scope_lv2': {'opt1': 10}}}},
|
|
especs={'capabilities:scope_lv0:scope_lv1:scope_lv2:opt1': '>= 2'},
|
|
passes=True)
|
|
|
|
def test_capability_filter_fails_wrong_scope_extra_specs(self):
|
|
self._do_test_type_filter_extra_specs(
|
|
ecaps={'scope_lv0': {'opt1': 10}},
|
|
especs={'capabilities:scope_lv1:opt1': '>= 2'},
|
|
passes=False)
|
|
|
|
def test_json_filter_passes(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
filter_properties = {'resource_type': {'memory_mb': 1024,
|
|
'root_gb': 200,
|
|
'ephemeral_gb': 0},
|
|
'scheduler_hints': {'query': self.json_query}}
|
|
capabilities = {'enabled': True}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 1024,
|
|
'free_disk_mb': 200 * 1024,
|
|
'capabilities': capabilities})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_passes_with_no_query(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
filter_properties = {'resource_type': {'memory_mb': 1024,
|
|
'root_gb': 200,
|
|
'ephemeral_gb': 0}}
|
|
capabilities = {'enabled': True}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 0,
|
|
'free_disk_mb': 0,
|
|
'capabilities': capabilities})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_fails_on_memory(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
filter_properties = {'resource_type': {'memory_mb': 1024,
|
|
'root_gb': 200,
|
|
'ephemeral_gb': 0},
|
|
'scheduler_hints': {'query': self.json_query}}
|
|
capabilities = {'enabled': True}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 1023,
|
|
'free_disk_mb': 200 * 1024,
|
|
'capabilities': capabilities})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_fails_on_disk(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
filter_properties = {'resource_type': {'memory_mb': 1024,
|
|
'root_gb': 200,
|
|
'ephemeral_gb': 0},
|
|
'scheduler_hints': {'query': self.json_query}}
|
|
capabilities = {'enabled': True}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 1024,
|
|
'free_disk_mb': (200 * 1024) - 1,
|
|
'capabilities': capabilities})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_fails_on_caps_disabled(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
json_query = jsonutils.dumps(
|
|
['and', ['>=', '$free_ram_mb', 1024],
|
|
['>=', '$free_disk_mb', 200 * 1024],
|
|
'$capabilities.enabled'])
|
|
filter_properties = {'resource_type': {'memory_mb': 1024,
|
|
'root_gb': 200,
|
|
'ephemeral_gb': 0},
|
|
'scheduler_hints': {'query': json_query}}
|
|
capabilities = {'enabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 1024,
|
|
'free_disk_mb': 200 * 1024,
|
|
'capabilities': capabilities})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_fails_on_service_disabled(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
json_query = jsonutils.dumps(
|
|
['and', ['>=', '$free_ram_mb', 1024],
|
|
['>=', '$free_disk_mb', 200 * 1024],
|
|
['not', '$service.disabled']])
|
|
filter_properties = {'resource_type': {'memory_mb': 1024,
|
|
'local_gb': 200},
|
|
'scheduler_hints': {'query': json_query}}
|
|
capabilities = {'enabled': True}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 1024,
|
|
'free_disk_mb': 200 * 1024,
|
|
'capabilities': capabilities})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_happy_day(self):
|
|
"""Test json filter more thoroughly."""
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
raw = ['and',
|
|
'$capabilities.enabled',
|
|
['=', '$capabilities.opt1', 'match'],
|
|
['or',
|
|
['and',
|
|
['<', '$free_ram_mb', 30],
|
|
['<', '$free_disk_mb', 300]],
|
|
['and',
|
|
['>', '$free_ram_mb', 30],
|
|
['>', '$free_disk_mb', 300]]]]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
|
|
# Passes
|
|
capabilities = {'enabled': True, 'opt1': 'match'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 10,
|
|
'free_disk_mb': 200,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
# Passes
|
|
capabilities = {'enabled': True, 'opt1': 'match'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 40,
|
|
'free_disk_mb': 400,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
# Fails due to capabilities being disabled
|
|
capabilities = {'enabled': False, 'opt1': 'match'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 40,
|
|
'free_disk_mb': 400,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
# Fails due to being exact memory/disk we don't want
|
|
capabilities = {'enabled': True, 'opt1': 'match'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 30,
|
|
'free_disk_mb': 300,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
# Fails due to memory lower but disk higher
|
|
capabilities = {'enabled': True, 'opt1': 'match'}
|
|
service = {'disabled': False}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 20,
|
|
'free_disk_mb': 400,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
# Fails due to capabilities 'opt1' not equal
|
|
capabilities = {'enabled': True, 'opt1': 'no-match'}
|
|
service = {'enabled': True}
|
|
host = fakes.FakeHostState('host1',
|
|
{'free_ram_mb': 20,
|
|
'free_disk_mb': 400,
|
|
'capabilities': capabilities,
|
|
'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_basic_operators(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
host = fakes.FakeHostState('host1',
|
|
{'capabilities': {'enabled': True}})
|
|
# (operator, arguments, expected_result)
|
|
ops_to_test = [
|
|
['=', [1, 1], True],
|
|
['=', [1, 2], False],
|
|
['<', [1, 2], True],
|
|
['<', [1, 1], False],
|
|
['<', [2, 1], False],
|
|
['>', [2, 1], True],
|
|
['>', [2, 2], False],
|
|
['>', [2, 3], False],
|
|
['<=', [1, 2], True],
|
|
['<=', [1, 1], True],
|
|
['<=', [2, 1], False],
|
|
['>=', [2, 1], True],
|
|
['>=', [2, 2], True],
|
|
['>=', [2, 3], False],
|
|
['in', [1, 1], True],
|
|
['in', [1, 1, 2, 3], True],
|
|
['in', [4, 1, 2, 3], False],
|
|
['not', [True], False],
|
|
['not', [False], True],
|
|
['or', [True, False], True],
|
|
['or', [False, False], False],
|
|
['and', [True, True], True],
|
|
['and', [False, False], False],
|
|
['and', [True, False], False],
|
|
# Nested ((True or False) and (2 > 1)) == Passes
|
|
['and', [['or', True, False], ['>', 2, 1]], True]]
|
|
|
|
for (op, args, expected) in ops_to_test:
|
|
raw = [op] + args
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertEqual(expected,
|
|
filt_cls.host_passes(host, filter_properties))
|
|
|
|
# This results in [False, True, False, True] and if any are True
|
|
# then it passes...
|
|
raw = ['not', True, False, True, False]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
# This results in [False, False, False] and if any are True
|
|
# then it passes...which this doesn't
|
|
raw = ['not', True, True, True]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_unknown_operator_raises(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
raw = ['!=', 1, 2]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
host = fakes.FakeHostState('host1',
|
|
{'capabilities': {'enabled': True}})
|
|
self.assertRaises(KeyError,
|
|
filt_cls.host_passes, host, filter_properties)
|
|
|
|
def test_json_filter_empty_filters_pass(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
host = fakes.FakeHostState('host1',
|
|
{'capabilities': {'enabled': True}})
|
|
|
|
raw = []
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
raw = {}
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_invalid_num_arguments_fails(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
host = fakes.FakeHostState('host1',
|
|
{'capabilities': {'enabled': True}})
|
|
|
|
raw = ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
raw = ['>', 1]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_json_filter_unknown_variable_ignored(self):
|
|
filt_cls = self.class_map['JsonFilter']()
|
|
host = fakes.FakeHostState('host1',
|
|
{'capabilities': {'enabled': True}})
|
|
|
|
raw = ['=', '$........', 1, 1]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
raw = ['=', '$foo', 2, 2]
|
|
filter_properties = {
|
|
'scheduler_hints': {
|
|
'query': jsonutils.dumps(raw),
|
|
},
|
|
}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
@staticmethod
|
|
def _make_zone_request(zone, is_admin=False):
|
|
ctxt = context.RequestContext('fake', 'fake', is_admin=is_admin)
|
|
return {
|
|
'context': ctxt,
|
|
'request_spec': {
|
|
'resource_properties': {
|
|
'availability_zone': zone
|
|
}
|
|
}
|
|
}
|
|
|
|
def test_availability_zone_filter_same(self):
|
|
filt_cls = self.class_map['AvailabilityZoneFilter']()
|
|
service = {'availability_zone': 'nova'}
|
|
request = self._make_zone_request('nova')
|
|
host = fakes.FakeHostState('host1',
|
|
{'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, request))
|
|
|
|
def test_availability_zone_filter_different(self):
|
|
filt_cls = self.class_map['AvailabilityZoneFilter']()
|
|
service = {'availability_zone': 'nova'}
|
|
request = self._make_zone_request('bad')
|
|
host = fakes.FakeHostState('host1',
|
|
{'service': service})
|
|
self.assertFalse(filt_cls.host_passes(host, request))
|
|
|
|
def test_availability_zone_filter_empty(self):
|
|
filt_cls = self.class_map['AvailabilityZoneFilter']()
|
|
service = {'availability_zone': 'nova'}
|
|
request = {}
|
|
host = fakes.FakeHostState('host1',
|
|
{'service': service})
|
|
self.assertTrue(filt_cls.host_passes(host, request))
|
|
|
|
def test_ignore_attempted_hosts_filter_disabled(self):
|
|
# Test case where re-scheduling is disabled.
|
|
filt_cls = self.class_map['IgnoreAttemptedHostsFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
filter_properties = {}
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_ignore_attempted_hosts_filter_pass(self):
|
|
# Node not previously tried.
|
|
filt_cls = self.class_map['IgnoreAttemptedHostsFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
attempted = dict(num_attempts=2, hosts=['host2'])
|
|
filter_properties = dict(retry=attempted)
|
|
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
|
|
|
def test_ignore_attempted_hosts_filter_fail(self):
|
|
# Node was already tried.
|
|
filt_cls = self.class_map['IgnoreAttemptedHostsFilter']()
|
|
host = fakes.FakeHostState('host1', {})
|
|
attempted = dict(num_attempts=2, hosts=['host1'])
|
|
filter_properties = dict(retry=attempted)
|
|
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|