325 lines
14 KiB
Python
325 lines
14 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 Filter Scheduler.
|
|
"""
|
|
|
|
import mock
|
|
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.scheduler import filter_scheduler
|
|
from nova.scheduler import host_manager
|
|
from nova.scheduler import utils as scheduler_utils
|
|
from nova.scheduler import weights
|
|
from nova import test # noqa
|
|
from nova.tests.unit.scheduler import test_scheduler
|
|
from nova.tests import uuidsentinel as uuids
|
|
|
|
|
|
class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|
"""Test case for Filter Scheduler."""
|
|
|
|
driver_cls = filter_scheduler.FilterScheduler
|
|
|
|
@mock.patch('nova.scheduler.filter_scheduler.FilterScheduler.'
|
|
'_get_all_host_states')
|
|
@mock.patch('nova.scheduler.filter_scheduler.FilterScheduler.'
|
|
'_get_sorted_hosts')
|
|
def test_schedule(self, mock_get_hosts, mock_get_all_states):
|
|
spec_obj = objects.RequestSpec(
|
|
num_instances=1,
|
|
flavor=objects.Flavor(memory_mb=512,
|
|
root_gb=512,
|
|
ephemeral_gb=0,
|
|
swap=0,
|
|
vcpus=1),
|
|
project_id=uuids.project_id,
|
|
instance_group=None)
|
|
|
|
host_state = mock.Mock(spec=host_manager.HostState,
|
|
host=mock.sentinel.host)
|
|
all_host_states = [host_state]
|
|
mock_get_all_states.return_value = all_host_states
|
|
mock_get_hosts.return_value = all_host_states
|
|
instance_uuids = [uuids.instance]
|
|
ctx = mock.Mock()
|
|
selected_hosts = self.driver._schedule(ctx, spec_obj,
|
|
instance_uuids, mock.sentinel.alloc_reqs_by_rp_uuid,
|
|
mock.sentinel.provider_summaries)
|
|
|
|
mock_get_all_states.assert_called_once_with(
|
|
ctx.elevated.return_value, spec_obj,
|
|
mock.sentinel.provider_summaries)
|
|
mock_get_hosts.assert_called_once_with(spec_obj, all_host_states, 0)
|
|
|
|
self.assertEqual(len(selected_hosts), 1)
|
|
self.assertEqual([host_state], selected_hosts)
|
|
|
|
# Ensure that we have consumed the resources on the chosen host states
|
|
host_state.consume_from_request.assert_called_once_with(spec_obj)
|
|
|
|
@mock.patch('nova.scheduler.filter_scheduler.FilterScheduler.'
|
|
'_get_all_host_states')
|
|
@mock.patch('nova.scheduler.filter_scheduler.FilterScheduler.'
|
|
'_get_sorted_hosts')
|
|
def test_schedule_instance_group(self, mock_get_hosts,
|
|
mock_get_all_states):
|
|
"""Test that since the request spec object contains an instance group
|
|
object, that upon choosing a host in the primary schedule loop,
|
|
that we update the request spec's instance group information
|
|
"""
|
|
num_instances = 2
|
|
ig = objects.InstanceGroup(hosts=[])
|
|
spec_obj = objects.RequestSpec(
|
|
num_instances=num_instances,
|
|
flavor=objects.Flavor(memory_mb=512,
|
|
root_gb=512,
|
|
ephemeral_gb=0,
|
|
swap=0,
|
|
vcpus=1),
|
|
project_id=uuids.project_id,
|
|
instance_group=ig)
|
|
|
|
hs1 = mock.Mock(spec=host_manager.HostState, host='host1')
|
|
hs2 = mock.Mock(spec=host_manager.HostState, host='host2')
|
|
all_host_states = [hs1, hs2]
|
|
mock_get_all_states.return_value = all_host_states
|
|
|
|
# Simulate host 1 and host 2 being randomly returned first by
|
|
# _get_sorted_hosts() in the two iterations for each instance in
|
|
# num_instances
|
|
mock_get_hosts.side_effect = ([hs2, hs1], [hs1, hs2])
|
|
instance_uuids = [
|
|
getattr(uuids, 'instance%d' % x) for x in range(num_instances)
|
|
]
|
|
ctx = mock.Mock()
|
|
self.driver._schedule(ctx, spec_obj, instance_uuids,
|
|
mock.sentinel.alloc_reqs_by_rp_uuid,
|
|
mock.sentinel.provider_summaries)
|
|
|
|
# Check that _get_sorted_hosts() is called twice and that the
|
|
# second time, we pass it the hosts that were returned from
|
|
# _get_sorted_hosts() the first time
|
|
sorted_host_calls = [
|
|
mock.call(spec_obj, all_host_states, 0),
|
|
mock.call(spec_obj, [hs2, hs1], 1),
|
|
]
|
|
mock_get_hosts.assert_has_calls(sorted_host_calls)
|
|
|
|
# The instance group object should have both host1 and host2 in its
|
|
# instance group hosts list and there should not be any "changes" to
|
|
# save in the instance group object
|
|
self.assertEqual(['host2', 'host1'], ig.hosts)
|
|
self.assertEqual({}, ig.obj_get_changes())
|
|
|
|
@mock.patch('random.choice', side_effect=lambda x: x[1])
|
|
@mock.patch('nova.scheduler.host_manager.HostManager.get_weighed_hosts')
|
|
@mock.patch('nova.scheduler.host_manager.HostManager.get_filtered_hosts')
|
|
def test_get_sorted_hosts(self, mock_filt, mock_weighed, mock_rand):
|
|
"""Tests the call that returns a sorted list of hosts by calling the
|
|
host manager's filtering and weighing routines
|
|
"""
|
|
self.flags(host_subset_size=2, group='filter_scheduler')
|
|
hs1 = mock.Mock(spec=host_manager.HostState, host='host1')
|
|
hs2 = mock.Mock(spec=host_manager.HostState, host='host2')
|
|
all_host_states = [hs1, hs2]
|
|
|
|
mock_weighed.return_value = [
|
|
weights.WeighedHost(hs1, 1.0), weights.WeighedHost(hs2, 1.0),
|
|
]
|
|
|
|
results = self.driver._get_sorted_hosts(mock.sentinel.spec,
|
|
all_host_states, mock.sentinel.index)
|
|
|
|
mock_filt.assert_called_once_with(all_host_states, mock.sentinel.spec,
|
|
mock.sentinel.index)
|
|
|
|
mock_weighed.assert_called_once_with(mock_filt.return_value,
|
|
mock.sentinel.spec)
|
|
|
|
# We override random.choice() to pick the **second** element of the
|
|
# returned weighed hosts list, which is the host state #2. This tests
|
|
# the code path that combines the randomly-chosen host with the
|
|
# remaining list of weighed host state objects
|
|
self.assertEqual([hs2, hs1], results)
|
|
|
|
@mock.patch('random.choice', side_effect=lambda x: x[0])
|
|
@mock.patch('nova.scheduler.host_manager.HostManager.get_weighed_hosts')
|
|
@mock.patch('nova.scheduler.host_manager.HostManager.get_filtered_hosts')
|
|
def test_get_sorted_hosts_subset_less_than_num_weighed(self, mock_filt,
|
|
mock_weighed, mock_rand):
|
|
"""Tests that when we have >1 weighed hosts but a host subset size of
|
|
1, that we always pick the first host in the weighed host
|
|
"""
|
|
self.flags(host_subset_size=1, group='filter_scheduler')
|
|
hs1 = mock.Mock(spec=host_manager.HostState, host='host1')
|
|
hs2 = mock.Mock(spec=host_manager.HostState, host='host2')
|
|
all_host_states = [hs1, hs2]
|
|
|
|
mock_weighed.return_value = [
|
|
weights.WeighedHost(hs1, 1.0), weights.WeighedHost(hs2, 1.0),
|
|
]
|
|
|
|
results = self.driver._get_sorted_hosts(mock.sentinel.spec,
|
|
all_host_states, mock.sentinel.index)
|
|
|
|
mock_filt.assert_called_once_with(all_host_states, mock.sentinel.spec,
|
|
mock.sentinel.index)
|
|
|
|
mock_weighed.assert_called_once_with(mock_filt.return_value,
|
|
mock.sentinel.spec)
|
|
|
|
# We should be randomly selecting only from a list of one host state
|
|
mock_rand.assert_called_once_with([hs1])
|
|
self.assertEqual([hs1, hs2], results)
|
|
|
|
@mock.patch('random.choice', side_effect=lambda x: x[0])
|
|
@mock.patch('nova.scheduler.host_manager.HostManager.get_weighed_hosts')
|
|
@mock.patch('nova.scheduler.host_manager.HostManager.get_filtered_hosts')
|
|
def test_get_sorted_hosts_subset_greater_than_num_weighed(self, mock_filt,
|
|
mock_weighed, mock_rand):
|
|
"""Hosts should still be chosen if host subset size is larger than
|
|
number of weighed hosts.
|
|
"""
|
|
self.flags(host_subset_size=20, group='filter_scheduler')
|
|
hs1 = mock.Mock(spec=host_manager.HostState, host='host1')
|
|
hs2 = mock.Mock(spec=host_manager.HostState, host='host2')
|
|
all_host_states = [hs1, hs2]
|
|
|
|
mock_weighed.return_value = [
|
|
weights.WeighedHost(hs1, 1.0), weights.WeighedHost(hs2, 1.0),
|
|
]
|
|
|
|
results = self.driver._get_sorted_hosts(mock.sentinel.spec,
|
|
all_host_states, mock.sentinel.index)
|
|
|
|
mock_filt.assert_called_once_with(all_host_states, mock.sentinel.spec,
|
|
mock.sentinel.index)
|
|
|
|
mock_weighed.assert_called_once_with(mock_filt.return_value,
|
|
mock.sentinel.spec)
|
|
|
|
# We overrode random.choice() to return the first element in the list,
|
|
# so even though we had a host_subset_size greater than the number of
|
|
# weighed hosts (2), we just random.choice() on the entire set of
|
|
# weighed hosts and thus return [hs1, hs2]
|
|
self.assertEqual([hs1, hs2], results)
|
|
|
|
def test_add_retry_host(self):
|
|
retry = dict(num_attempts=1, hosts=[])
|
|
filter_properties = dict(retry=retry)
|
|
host = "fakehost"
|
|
node = "fakenode"
|
|
|
|
scheduler_utils._add_retry_host(filter_properties, host, node)
|
|
|
|
hosts = filter_properties['retry']['hosts']
|
|
self.assertEqual(1, len(hosts))
|
|
self.assertEqual([host, node], hosts[0])
|
|
|
|
def test_post_select_populate(self):
|
|
# Test addition of certain filter props after a node is selected.
|
|
retry = {'hosts': [], 'num_attempts': 1}
|
|
filter_properties = {'retry': retry}
|
|
|
|
host_state = host_manager.HostState('host', 'node', uuids.cell)
|
|
host_state.limits['vcpu'] = 5
|
|
scheduler_utils.populate_filter_properties(filter_properties,
|
|
host_state)
|
|
|
|
self.assertEqual(['host', 'node'],
|
|
filter_properties['retry']['hosts'][0])
|
|
|
|
self.assertEqual({'vcpu': 5}, host_state.limits)
|
|
|
|
@mock.patch('nova.scheduler.filter_scheduler.FilterScheduler.'
|
|
'_schedule')
|
|
def test_select_destinations_match_num_instances(self, mock_schedule):
|
|
"""Tests that the select_destinations() method returns the list of
|
|
hosts from the _schedule() method when the number of returned hosts
|
|
equals the num_instances on the spec object
|
|
"""
|
|
spec_obj = objects.RequestSpec(
|
|
flavor=objects.Flavor(memory_mb=512,
|
|
root_gb=512,
|
|
ephemeral_gb=0,
|
|
swap=0,
|
|
vcpus=1),
|
|
project_id=uuids.project_id,
|
|
num_instances=1)
|
|
|
|
mock_schedule.return_value = [mock.sentinel.hs1]
|
|
|
|
dests = self.driver.select_destinations(self.context, spec_obj,
|
|
mock.sentinel.instance_uuids, mock.sentinel.alloc_reqs_by_rp_uuid,
|
|
mock.sentinel.p_sums)
|
|
|
|
mock_schedule.assert_called_once_with(self.context, spec_obj,
|
|
mock.sentinel.instance_uuids, mock.sentinel.alloc_reqs_by_rp_uuid,
|
|
mock.sentinel.p_sums)
|
|
|
|
self.assertEqual([mock.sentinel.hs1], dests)
|
|
|
|
@mock.patch('nova.scheduler.filter_scheduler.FilterScheduler.'
|
|
'_schedule')
|
|
def test_select_destinations_fewer_num_instances(self, mock_schedule):
|
|
"""Tests that the select_destinations() method properly handles
|
|
resetting host state objects and raising NoValidHost when the
|
|
_schedule() method returns no host matches.
|
|
"""
|
|
spec_obj = objects.RequestSpec(
|
|
flavor=objects.Flavor(memory_mb=512,
|
|
root_gb=512,
|
|
ephemeral_gb=0,
|
|
swap=0,
|
|
vcpus=1),
|
|
project_id=uuids.project_id,
|
|
num_instances=2)
|
|
|
|
host_state = mock.Mock(spec=host_manager.HostState)
|
|
mock_schedule.return_value = [host_state]
|
|
|
|
self.assertRaises(exception.NoValidHost,
|
|
self.driver.select_destinations, self.context, spec_obj,
|
|
mock.sentinel.instance_uuids, mock.sentinel.alloc_reqs_by_rp_uuid,
|
|
mock.sentinel.p_sums)
|
|
|
|
# Verify that the host state object has been marked as not updated so
|
|
# it's picked up in the next pull from the DB for compute node objects
|
|
self.assertIsNone(host_state.updated)
|
|
|
|
@mock.patch.object(filter_scheduler.FilterScheduler, '_schedule')
|
|
def test_select_destinations_notifications(self, mock_schedule):
|
|
mock_schedule.return_value = [mock.Mock()]
|
|
|
|
with mock.patch.object(self.driver.notifier, 'info') as mock_info:
|
|
expected = {'num_instances': 1,
|
|
'instance_properties': {'uuid': uuids.instance},
|
|
'instance_type': {},
|
|
'image': {}}
|
|
spec_obj = objects.RequestSpec(num_instances=1,
|
|
instance_uuid=uuids.instance)
|
|
|
|
self.driver.select_destinations(self.context, spec_obj,
|
|
[uuids.instance], {}, None)
|
|
|
|
expected = [
|
|
mock.call(self.context, 'scheduler.select_destinations.start',
|
|
dict(request_spec=expected)),
|
|
mock.call(self.context, 'scheduler.select_destinations.end',
|
|
dict(request_spec=expected))]
|
|
self.assertEqual(expected, mock_info.call_args_list)
|