1384 lines
56 KiB
Python
1384 lines
56 KiB
Python
# 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.
|
|
|
|
from keystoneauth1 import exceptions as ks_exc
|
|
import mock
|
|
import six
|
|
from six.moves.urllib import parse
|
|
|
|
import nova.conf
|
|
from nova import context
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.objects import base as obj_base
|
|
from nova.scheduler.client import report
|
|
from nova import test
|
|
from nova.tests import uuidsentinel as uuids
|
|
|
|
CONF = nova.conf.CONF
|
|
|
|
|
|
class SafeConnectedTestCase(test.NoDBTestCase):
|
|
"""Test the safe_connect decorator for the scheduler client."""
|
|
|
|
def setUp(self):
|
|
super(SafeConnectedTestCase, self).setUp()
|
|
self.context = context.get_admin_context()
|
|
self.ks_sess_mock = mock.Mock()
|
|
|
|
with test.nested(
|
|
mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
|
) as _auth_mock: # noqa
|
|
self.client = report.SchedulerReportClient()
|
|
|
|
@mock.patch('keystoneauth1.session.Session.request')
|
|
def test_missing_endpoint(self, req):
|
|
"""Test EndpointNotFound behavior.
|
|
|
|
A missing endpoint entry should not explode.
|
|
"""
|
|
req.side_effect = ks_exc.EndpointNotFound()
|
|
self.client._get_resource_provider("fake")
|
|
|
|
# reset the call count to demonstrate that future calls still
|
|
# work
|
|
req.reset_mock()
|
|
self.client._get_resource_provider("fake")
|
|
self.assertTrue(req.called)
|
|
|
|
@mock.patch('keystoneauth1.session.Session.request')
|
|
def test_missing_auth(self, req):
|
|
"""Test Missing Auth handled correctly.
|
|
|
|
A missing auth configuration should not explode.
|
|
|
|
"""
|
|
req.side_effect = ks_exc.MissingAuthPlugin()
|
|
self.client._get_resource_provider("fake")
|
|
|
|
# reset the call count to demonstrate that future calls still
|
|
# work
|
|
req.reset_mock()
|
|
self.client._get_resource_provider("fake")
|
|
self.assertTrue(req.called)
|
|
|
|
@mock.patch('keystoneauth1.session.Session.request')
|
|
def test_unauthorized(self, req):
|
|
"""Test Unauthorized handled correctly.
|
|
|
|
An unauthorized configuration should not explode.
|
|
|
|
"""
|
|
req.side_effect = ks_exc.Unauthorized()
|
|
self.client._get_resource_provider("fake")
|
|
|
|
# reset the call count to demonstrate that future calls still
|
|
# work
|
|
req.reset_mock()
|
|
self.client._get_resource_provider("fake")
|
|
self.assertTrue(req.called)
|
|
|
|
@mock.patch('keystoneauth1.session.Session.request')
|
|
def test_connect_fail(self, req):
|
|
"""Test Connect Failure handled correctly.
|
|
|
|
If we get a connect failure, this is transient, and we expect
|
|
that this will end up working correctly later.
|
|
|
|
"""
|
|
req.side_effect = ks_exc.ConnectFailure()
|
|
self.client._get_resource_provider("fake")
|
|
|
|
# reset the call count to demonstrate that future calls do
|
|
# work
|
|
req.reset_mock()
|
|
self.client._get_resource_provider("fake")
|
|
self.assertTrue(req.called)
|
|
|
|
@mock.patch.object(report, 'LOG')
|
|
def test_warning_limit(self, mock_log):
|
|
# Assert that __init__ initializes _warn_count as we expect
|
|
self.assertEqual(0, self.client._warn_count)
|
|
mock_self = mock.MagicMock()
|
|
mock_self._warn_count = 0
|
|
for i in range(0, report.WARN_EVERY + 3):
|
|
report.warn_limit(mock_self, 'warning')
|
|
mock_log.warning.assert_has_calls([mock.call('warning'),
|
|
mock.call('warning')])
|
|
|
|
@mock.patch('keystoneauth1.session.Session.request')
|
|
def test_failed_discovery(self, req):
|
|
"""Test DiscoveryFailure behavior.
|
|
|
|
Failed discovery should not blow up.
|
|
"""
|
|
req.side_effect = ks_exc.DiscoveryFailure()
|
|
self.client._get_resource_provider("fake")
|
|
|
|
# reset the call count to demonstrate that future calls still
|
|
# work
|
|
req.reset_mock()
|
|
self.client._get_resource_provider("fake")
|
|
self.assertTrue(req.called)
|
|
|
|
|
|
class TestConstructor(test.NoDBTestCase):
|
|
@mock.patch('keystoneauth1.session.Session')
|
|
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
|
def test_constructor(self, load_auth_mock, ks_sess_mock):
|
|
report.SchedulerReportClient()
|
|
|
|
load_auth_mock.assert_called_once_with(CONF, 'placement')
|
|
ks_sess_mock.assert_called_once_with(auth=load_auth_mock.return_value)
|
|
|
|
|
|
class SchedulerReportClientTestCase(test.NoDBTestCase):
|
|
|
|
def setUp(self):
|
|
super(SchedulerReportClientTestCase, self).setUp()
|
|
self.context = context.get_admin_context()
|
|
self.ks_sess_mock = mock.Mock()
|
|
self.compute_node = objects.ComputeNode(
|
|
uuid=uuids.compute_node,
|
|
hypervisor_hostname='foo',
|
|
vcpus=8,
|
|
cpu_allocation_ratio=16.0,
|
|
memory_mb=1024,
|
|
ram_allocation_ratio=1.5,
|
|
local_gb=10,
|
|
disk_allocation_ratio=1.0,
|
|
)
|
|
|
|
with test.nested(
|
|
mock.patch('keystoneauth1.session.Session',
|
|
return_value=self.ks_sess_mock),
|
|
mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
|
) as (_auth_mock, _sess_mock):
|
|
self.client = report.SchedulerReportClient()
|
|
|
|
|
|
class TestProviderOperations(SchedulerReportClientTestCase):
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_create_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_provider_aggregates')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_resource_provider')
|
|
def test_ensure_resource_provider_exists_in_cache(self, get_rp_mock,
|
|
get_agg_mock, create_rp_mock):
|
|
# Override the client object's cache to contain a resource provider
|
|
# object for the compute host and check that
|
|
# _ensure_resource_provider() doesn't call _get_resource_provider() or
|
|
# _create_resource_provider()
|
|
self.client._resource_providers = {
|
|
uuids.compute_node: mock.sentinel.rp
|
|
}
|
|
|
|
self.client._ensure_resource_provider(uuids.compute_node)
|
|
get_agg_mock.assert_called_once_with(uuids.compute_node)
|
|
self.assertIn(uuids.compute_node, self.client._provider_aggregate_map)
|
|
self.assertEqual(
|
|
get_agg_mock.return_value,
|
|
self.client._provider_aggregate_map[uuids.compute_node]
|
|
)
|
|
self.assertFalse(get_rp_mock.called)
|
|
self.assertFalse(create_rp_mock.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_create_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_provider_aggregates')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_resource_provider')
|
|
def test_ensure_resource_provider_get(self, get_rp_mock, get_agg_mock,
|
|
create_rp_mock):
|
|
# No resource provider exists in the client's cache, so validate that
|
|
# if we get the resource provider from the placement API that we don't
|
|
# try to create the resource provider.
|
|
get_rp_mock.return_value = mock.sentinel.rp
|
|
|
|
self.client._ensure_resource_provider(uuids.compute_node)
|
|
|
|
get_rp_mock.assert_called_once_with(uuids.compute_node)
|
|
get_agg_mock.assert_called_once_with(uuids.compute_node)
|
|
self.assertIn(uuids.compute_node, self.client._provider_aggregate_map)
|
|
self.assertEqual(
|
|
get_agg_mock.return_value,
|
|
self.client._provider_aggregate_map[uuids.compute_node]
|
|
)
|
|
self.assertEqual({uuids.compute_node: mock.sentinel.rp},
|
|
self.client._resource_providers)
|
|
self.assertFalse(create_rp_mock.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_create_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_provider_aggregates')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_resource_provider')
|
|
def test_ensure_resource_provider_create_none(self, get_rp_mock,
|
|
get_agg_mock, create_rp_mock):
|
|
# No resource provider exists in the client's cache, and
|
|
# _create_provider returns None, indicating there was an error with the
|
|
# create call. Ensure we don't populate the resource provider cache
|
|
# with a None value.
|
|
get_rp_mock.return_value = None
|
|
create_rp_mock.return_value = None
|
|
|
|
self.client._ensure_resource_provider(uuids.compute_node)
|
|
|
|
get_rp_mock.assert_called_once_with(uuids.compute_node)
|
|
create_rp_mock.assert_called_once_with(uuids.compute_node,
|
|
uuids.compute_node)
|
|
self.assertFalse(get_agg_mock.called)
|
|
self.assertEqual({}, self.client._resource_providers)
|
|
self.assertEqual({}, self.client._provider_aggregate_map)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_create_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_provider_aggregates')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_resource_provider')
|
|
def test_ensure_resource_provider_create(self, get_rp_mock, get_agg_mock,
|
|
create_rp_mock):
|
|
# No resource provider exists in the client's cache and no resource
|
|
# provider was returned from the placement API, so verify that in this
|
|
# case we try to create the resource provider via the placement API.
|
|
get_rp_mock.return_value = None
|
|
create_rp_mock.return_value = mock.sentinel.rp
|
|
|
|
self.client._ensure_resource_provider(uuids.compute_node)
|
|
|
|
get_agg_mock.assert_called_once_with(uuids.compute_node)
|
|
self.assertIn(uuids.compute_node, self.client._provider_aggregate_map)
|
|
self.assertEqual(
|
|
get_agg_mock.return_value,
|
|
self.client._provider_aggregate_map[uuids.compute_node]
|
|
)
|
|
get_rp_mock.assert_called_once_with(uuids.compute_node)
|
|
create_rp_mock.assert_called_once_with(
|
|
uuids.compute_node,
|
|
uuids.compute_node, # name param defaults to UUID if None
|
|
)
|
|
self.assertEqual({uuids.compute_node: mock.sentinel.rp},
|
|
self.client._resource_providers)
|
|
|
|
create_rp_mock.reset_mock()
|
|
self.client._resource_providers = {}
|
|
|
|
self.client._ensure_resource_provider(uuids.compute_node,
|
|
mock.sentinel.name)
|
|
|
|
create_rp_mock.assert_called_once_with(
|
|
uuids.compute_node,
|
|
mock.sentinel.name,
|
|
)
|
|
|
|
def test_get_filtered_resource_providers(self):
|
|
uuid = uuids.compute_node
|
|
resp_mock = mock.Mock(status_code=200)
|
|
json_data = {
|
|
'resource_providers': [
|
|
{'uuid': uuid,
|
|
'name': uuid,
|
|
'generation': 42}
|
|
],
|
|
}
|
|
filters = {'resources': {'VCPU': 1, 'MEMORY_MB': 1024}}
|
|
resp_mock.json.return_value = json_data
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
result = self.client.get_filtered_resource_providers(filters)
|
|
|
|
expected_provider = objects.ResourceProvider(
|
|
uuid=uuid,
|
|
name=uuid,
|
|
generation=42,
|
|
)
|
|
expected_url = '/resource_providers?%s' % parse.urlencode(
|
|
{'resources': 'MEMORY_MB:1024,VCPU:1'})
|
|
self.ks_sess_mock.get.assert_called_once_with(
|
|
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
|
|
headers={'OpenStack-API-Version': 'placement 1.4'})
|
|
self.assertTrue(obj_base.obj_equal_prims(expected_provider,
|
|
result[0]))
|
|
|
|
def test_get_filtered_resource_providers_not_found(self):
|
|
# Ensure _get_resource_provider() just returns None when the placement
|
|
# API doesn't find a resource provider matching a UUID
|
|
resp_mock = mock.Mock(status_code=404)
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
result = self.client.get_filtered_resource_providers({'foo': 'bar'})
|
|
|
|
expected_url = '/resource_providers?foo=bar'
|
|
self.ks_sess_mock.get.assert_called_once_with(
|
|
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
|
|
headers={'OpenStack-API-Version': 'placement 1.4'})
|
|
self.assertIsNone(result)
|
|
|
|
def test_get_resource_provider_found(self):
|
|
# Ensure _get_resource_provider() returns a ResourceProvider object if
|
|
# it finds a resource provider record from the placement API
|
|
uuid = uuids.compute_node
|
|
resp_mock = mock.Mock(status_code=200)
|
|
json_data = {
|
|
'uuid': uuid,
|
|
'name': uuid,
|
|
'generation': 42,
|
|
}
|
|
resp_mock.json.return_value = json_data
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
result = self.client._get_resource_provider(uuid)
|
|
|
|
expected_provider = objects.ResourceProvider(
|
|
uuid=uuid,
|
|
name=uuid,
|
|
generation=42,
|
|
)
|
|
expected_url = '/resource_providers/' + uuid
|
|
self.ks_sess_mock.get.assert_called_once_with(expected_url,
|
|
endpoint_filter=mock.ANY,
|
|
raise_exc=False)
|
|
self.assertTrue(obj_base.obj_equal_prims(expected_provider,
|
|
result))
|
|
|
|
def test_get_resource_provider_not_found(self):
|
|
# Ensure _get_resource_provider() just returns None when the placement
|
|
# API doesn't find a resource provider matching a UUID
|
|
resp_mock = mock.Mock(status_code=404)
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
uuid = uuids.compute_node
|
|
result = self.client._get_resource_provider(uuid)
|
|
|
|
expected_url = '/resource_providers/' + uuid
|
|
self.ks_sess_mock.get.assert_called_once_with(expected_url,
|
|
endpoint_filter=mock.ANY,
|
|
raise_exc=False)
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(report.LOG, 'error')
|
|
def test_get_resource_provider_error(self, logging_mock):
|
|
# Ensure _get_resource_provider() sets the error flag when trying to
|
|
# communicate with the placement API and not getting an error we can
|
|
# deal with
|
|
resp_mock = mock.Mock(status_code=503)
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
uuid = uuids.compute_node
|
|
result = self.client._get_resource_provider(uuid)
|
|
|
|
expected_url = '/resource_providers/' + uuid
|
|
self.ks_sess_mock.get.assert_called_once_with(expected_url,
|
|
endpoint_filter=mock.ANY,
|
|
raise_exc=False)
|
|
# A 503 Service Unavailable should trigger an error logged and
|
|
# return None from _get_resource_provider()
|
|
self.assertTrue(logging_mock.called)
|
|
self.assertIsNone(result)
|
|
|
|
def test_create_resource_provider(self):
|
|
# Ensure _create_resource_provider() returns a ResourceProvider object
|
|
# constructed after creating a resource provider record in the
|
|
# placement API
|
|
uuid = uuids.compute_node
|
|
name = 'computehost'
|
|
resp_mock = mock.Mock(status_code=201)
|
|
self.ks_sess_mock.post.return_value = resp_mock
|
|
|
|
result = self.client._create_resource_provider(uuid, name)
|
|
|
|
expected_payload = {
|
|
'uuid': uuid,
|
|
'name': name,
|
|
}
|
|
expected_provider = objects.ResourceProvider(
|
|
uuid=uuid,
|
|
name=name,
|
|
generation=0,
|
|
)
|
|
expected_url = '/resource_providers'
|
|
self.ks_sess_mock.post.assert_called_once_with(
|
|
expected_url,
|
|
endpoint_filter=mock.ANY,
|
|
json=expected_payload,
|
|
raise_exc=False)
|
|
self.assertTrue(obj_base.obj_equal_prims(expected_provider,
|
|
result))
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_resource_provider')
|
|
def test_create_resource_provider_concurrent_create(self, get_rp_mock):
|
|
# Ensure _create_resource_provider() returns a ResourceProvider object
|
|
# gotten from _get_resource_provider() if the call to create the
|
|
# resource provider in the placement API returned a 409 Conflict,
|
|
# indicating another thread concurrently created the resource provider
|
|
# record.
|
|
uuid = uuids.compute_node
|
|
name = 'computehost'
|
|
resp_mock = mock.Mock(status_code=409)
|
|
self.ks_sess_mock.post.return_value = resp_mock
|
|
|
|
get_rp_mock.return_value = mock.sentinel.get_rp
|
|
|
|
result = self.client._create_resource_provider(uuid, name)
|
|
|
|
expected_payload = {
|
|
'uuid': uuid,
|
|
'name': name,
|
|
}
|
|
expected_url = '/resource_providers'
|
|
self.ks_sess_mock.post.assert_called_once_with(
|
|
expected_url,
|
|
endpoint_filter=mock.ANY,
|
|
json=expected_payload,
|
|
raise_exc=False)
|
|
self.assertEqual(mock.sentinel.get_rp, result)
|
|
|
|
@mock.patch.object(report.LOG, 'error')
|
|
def test_create_resource_provider_error(self, logging_mock):
|
|
# Ensure _create_resource_provider() sets the error flag when trying to
|
|
# communicate with the placement API and not getting an error we can
|
|
# deal with
|
|
uuid = uuids.compute_node
|
|
name = 'computehost'
|
|
resp_mock = mock.Mock(status_code=503)
|
|
self.ks_sess_mock.post.return_value = resp_mock
|
|
|
|
result = self.client._create_resource_provider(uuid, name)
|
|
|
|
expected_payload = {
|
|
'uuid': uuid,
|
|
'name': name,
|
|
}
|
|
expected_url = '/resource_providers'
|
|
self.ks_sess_mock.post.assert_called_once_with(
|
|
expected_url,
|
|
endpoint_filter=mock.ANY,
|
|
json=expected_payload,
|
|
raise_exc=False)
|
|
# A 503 Service Unavailable should log an error and
|
|
# _create_resource_provider() should return None
|
|
self.assertTrue(logging_mock.called)
|
|
self.assertFalse(result)
|
|
|
|
|
|
class TestAggregates(SchedulerReportClientTestCase):
|
|
def test_get_provider_aggregates_found(self):
|
|
"""Test that when the placement API returns a list of aggregate UUIDs,
|
|
that we cache that aggregate information in the appropriate map.
|
|
"""
|
|
uuid = uuids.compute_node
|
|
resp_mock = mock.Mock(status_code=200)
|
|
json_data = {
|
|
'aggregates': [
|
|
uuids.agg1,
|
|
uuids.agg2,
|
|
],
|
|
}
|
|
resp_mock.json.return_value = json_data
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
result = self.client._get_provider_aggregates(uuid)
|
|
|
|
expected = set([
|
|
uuids.agg1,
|
|
uuids.agg2,
|
|
])
|
|
expected_url = '/resource_providers/' + uuid + '/aggregates'
|
|
self.ks_sess_mock.get.assert_called_once_with(
|
|
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
|
|
headers={'OpenStack-API-Version': 'placement 1.1'})
|
|
self.assertTrue(expected, result)
|
|
|
|
@mock.patch.object(report.LOG, 'warning')
|
|
def test_get_provider_aggregates_not_found(self, log_mock):
|
|
"""Test that when the placement API returns a 404 when looking up a
|
|
provider's aggregates, that we simply return None and log a warning
|
|
(since _get_provider_aggregates() should be called after
|
|
_ensure_resource_provider()).
|
|
"""
|
|
uuid = uuids.compute_node
|
|
resp_mock = mock.Mock(status_code=404)
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
result = self.client._get_provider_aggregates(uuid)
|
|
|
|
expected_url = '/resource_providers/' + uuid + '/aggregates'
|
|
self.ks_sess_mock.get.assert_called_once_with(
|
|
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
|
|
headers={'OpenStack-API-Version': 'placement 1.1'})
|
|
self.assertTrue(log_mock.called)
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(report.LOG, 'error')
|
|
def test_get_provider_aggregates_bad_request(self, log_mock):
|
|
"""Test that when the placement API returns a 400 when looking up a
|
|
provider's aggregates, that we simply return None and log an error.
|
|
"""
|
|
uuid = uuids.compute_node
|
|
resp_mock = mock.Mock(status_code=400)
|
|
self.ks_sess_mock.get.return_value = resp_mock
|
|
|
|
result = self.client._get_provider_aggregates(uuid)
|
|
|
|
expected_url = '/resource_providers/' + uuid + '/aggregates'
|
|
self.ks_sess_mock.get.assert_called_once_with(
|
|
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
|
|
headers={'OpenStack-API-Version': 'placement 1.1'})
|
|
self.assertTrue(log_mock.called)
|
|
self.assertIsNone(result)
|
|
|
|
|
|
class TestComputeNodeToInventoryDict(test.NoDBTestCase):
|
|
def test_compute_node_inventory(self):
|
|
uuid = uuids.compute_node
|
|
name = 'computehost'
|
|
compute_node = objects.ComputeNode(uuid=uuid,
|
|
hypervisor_hostname=name,
|
|
vcpus=2,
|
|
cpu_allocation_ratio=16.0,
|
|
memory_mb=1024,
|
|
ram_allocation_ratio=1.5,
|
|
local_gb=10,
|
|
disk_allocation_ratio=1.0)
|
|
|
|
self.flags(reserved_host_memory_mb=1000)
|
|
self.flags(reserved_host_disk_mb=200)
|
|
|
|
result = report._compute_node_to_inventory_dict(compute_node)
|
|
|
|
expected = {
|
|
'VCPU': {
|
|
'total': compute_node.vcpus,
|
|
'reserved': 0,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.vcpus,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.cpu_allocation_ratio,
|
|
},
|
|
'MEMORY_MB': {
|
|
'total': compute_node.memory_mb,
|
|
'reserved': CONF.reserved_host_memory_mb,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.memory_mb,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.ram_allocation_ratio,
|
|
},
|
|
'DISK_GB': {
|
|
'total': compute_node.local_gb,
|
|
'reserved': 1, # this is ceil(1000/1024)
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.local_gb,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.disk_allocation_ratio,
|
|
},
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
def test_compute_node_inventory_empty(self):
|
|
uuid = uuids.compute_node
|
|
name = 'computehost'
|
|
compute_node = objects.ComputeNode(uuid=uuid,
|
|
hypervisor_hostname=name,
|
|
vcpus=0,
|
|
cpu_allocation_ratio=16.0,
|
|
memory_mb=0,
|
|
ram_allocation_ratio=1.5,
|
|
local_gb=0,
|
|
disk_allocation_ratio=1.0)
|
|
result = report._compute_node_to_inventory_dict(compute_node)
|
|
self.assertEqual({}, result)
|
|
|
|
|
|
class TestInventory(SchedulerReportClientTestCase):
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_ensure_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_delete_inventory')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_update_inventory')
|
|
@mock.patch('nova.objects.ComputeNode.save')
|
|
def test_update_resource_stats(self, mock_save, mock_ui, mock_delete,
|
|
mock_erp):
|
|
cn = self.compute_node
|
|
self.client.update_resource_stats(cn)
|
|
mock_save.assert_called_once_with()
|
|
mock_erp.assert_called_once_with(cn.uuid, cn.hypervisor_hostname)
|
|
expected_inv_data = {
|
|
'VCPU': {
|
|
'total': 8,
|
|
'reserved': 0,
|
|
'min_unit': 1,
|
|
'max_unit': 8,
|
|
'step_size': 1,
|
|
'allocation_ratio': 16.0,
|
|
},
|
|
'MEMORY_MB': {
|
|
'total': 1024,
|
|
'reserved': 512,
|
|
'min_unit': 1,
|
|
'max_unit': 1024,
|
|
'step_size': 1,
|
|
'allocation_ratio': 1.5,
|
|
},
|
|
'DISK_GB': {
|
|
'total': 10,
|
|
'reserved': 0,
|
|
'min_unit': 1,
|
|
'max_unit': 10,
|
|
'step_size': 1,
|
|
'allocation_ratio': 1.0,
|
|
},
|
|
}
|
|
mock_ui.assert_called_once_with(
|
|
cn.uuid,
|
|
expected_inv_data,
|
|
)
|
|
self.assertFalse(mock_delete.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_ensure_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_delete_inventory')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_update_inventory')
|
|
@mock.patch('nova.objects.ComputeNode.save')
|
|
def test_update_resource_stats_no_inv(self, mock_save, mock_ui,
|
|
mock_delete, mock_erp):
|
|
"""Ensure that if there are no inventory records, that we call
|
|
_delete_inventory() instead of _update_inventory().
|
|
"""
|
|
cn = self.compute_node
|
|
cn.vcpus = 0
|
|
cn.memory_mb = 0
|
|
cn.local_gb = 0
|
|
self.client.update_resource_stats(cn)
|
|
mock_save.assert_called_once_with()
|
|
mock_erp.assert_called_once_with(cn.uuid, cn.hypervisor_hostname)
|
|
mock_delete.assert_called_once_with(cn.uuid)
|
|
self.assertFalse(mock_ui.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report._extract_inventory_in_use')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
def test_delete_inventory_already_no_inventory(self, mock_get, mock_put,
|
|
mock_extract):
|
|
cn = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=cn.uuid, generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[cn.uuid] = rp
|
|
|
|
mock_get.return_value.json.return_value = {
|
|
'resource_provider_generation': 1,
|
|
'inventories': {
|
|
}
|
|
}
|
|
result = self.client._delete_inventory(cn.uuid)
|
|
self.assertIsNone(result)
|
|
self.assertFalse(mock_put.called)
|
|
self.assertFalse(mock_extract.called)
|
|
new_gen = self.client._resource_providers[cn.uuid].generation
|
|
self.assertEqual(1, new_gen)
|
|
|
|
@mock.patch('nova.scheduler.client.report._extract_inventory_in_use')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
def test_delete_inventory(self, mock_get, mock_put, mock_extract):
|
|
cn = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=cn.uuid, generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[cn.uuid] = rp
|
|
|
|
mock_get.return_value.json.return_value = {
|
|
'resource_provider_generation': 1,
|
|
'inventories': {
|
|
'VCPU': {'total': 16},
|
|
'MEMORY_MB': {'total': 1024},
|
|
'DISK_GB': {'total': 10},
|
|
}
|
|
}
|
|
mock_put.return_value.status_code = 200
|
|
mock_put.return_value.json.return_value = {
|
|
'resource_provider_generation': 44,
|
|
'inventories': {
|
|
}
|
|
}
|
|
result = self.client._delete_inventory(cn.uuid)
|
|
self.assertIsNone(result)
|
|
self.assertFalse(mock_extract.called)
|
|
new_gen = self.client._resource_providers[cn.uuid].generation
|
|
self.assertEqual(44, new_gen)
|
|
|
|
@mock.patch.object(report.LOG, 'warning')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
def test_delete_inventory_inventory_in_use(self, mock_get, mock_put,
|
|
mock_warn):
|
|
cn = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=cn.uuid, generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[cn.uuid] = rp
|
|
|
|
mock_get.return_value.json.return_value = {
|
|
'resource_provider_generation': 1,
|
|
'inventories': {
|
|
'VCPU': {'total': 16},
|
|
'MEMORY_MB': {'total': 1024},
|
|
'DISK_GB': {'total': 10},
|
|
}
|
|
}
|
|
mock_put.return_value.status_code = 409
|
|
rc_str = "VCPU, MEMORY_MB"
|
|
in_use_exc = exception.InventoryInUse(
|
|
resource_classes=rc_str,
|
|
resource_provider=cn.uuid,
|
|
)
|
|
fault_text = """
|
|
409 Conflict
|
|
|
|
There was a conflict when trying to complete your request.
|
|
|
|
update conflict: %s
|
|
""" % six.text_type(in_use_exc)
|
|
mock_put.return_value.text = fault_text
|
|
mock_put.return_value.json.return_value = {
|
|
'resource_provider_generation': 44,
|
|
'inventories': {
|
|
}
|
|
}
|
|
result = self.client._delete_inventory(cn.uuid)
|
|
self.assertIsNone(result)
|
|
self.assertTrue(mock_warn.called)
|
|
|
|
@mock.patch.object(report.LOG, 'error')
|
|
@mock.patch.object(report.LOG, 'warning')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
def test_delete_inventory_inventory_error(self, mock_get, mock_put,
|
|
mock_warn, mock_error):
|
|
cn = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=cn.uuid, generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[cn.uuid] = rp
|
|
|
|
mock_get.return_value.json.return_value = {
|
|
'resource_provider_generation': 1,
|
|
'inventories': {
|
|
'VCPU': {'total': 16},
|
|
'MEMORY_MB': {'total': 1024},
|
|
'DISK_GB': {'total': 10},
|
|
}
|
|
}
|
|
mock_put.return_value.status_code = 409
|
|
mock_put.return_value.text = (
|
|
'There was a failure'
|
|
)
|
|
mock_put.return_value.json.return_value = {
|
|
'resource_provider_generation': 44,
|
|
'inventories': {
|
|
}
|
|
}
|
|
result = self.client._delete_inventory(cn.uuid)
|
|
self.assertIsNone(result)
|
|
self.assertFalse(mock_warn.called)
|
|
self.assertTrue(mock_error.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
def test_update_inventory(self, mock_put, mock_get):
|
|
# Ensure _update_inventory() returns a list of Inventories objects
|
|
# after creating or updating the existing values
|
|
uuid = uuids.compute_node
|
|
compute_node = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[uuid] = rp
|
|
|
|
mock_get.return_value.json.return_value = {
|
|
'resource_provider_generation': 43,
|
|
'inventories': {
|
|
'VCPU': {'total': 16},
|
|
'MEMORY_MB': {'total': 1024},
|
|
'DISK_GB': {'total': 10},
|
|
}
|
|
}
|
|
mock_put.return_value.status_code = 200
|
|
mock_put.return_value.json.return_value = {
|
|
'resource_provider_generation': 44,
|
|
'inventories': {
|
|
'VCPU': {'total': 16},
|
|
'MEMORY_MB': {'total': 1024},
|
|
'DISK_GB': {'total': 10},
|
|
}
|
|
}
|
|
|
|
inv_data = report._compute_node_to_inventory_dict(compute_node)
|
|
result = self.client._update_inventory_attempt(
|
|
compute_node.uuid, inv_data
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
exp_url = '/resource_providers/%s/inventories' % uuid
|
|
mock_get.assert_called_once_with(exp_url)
|
|
# Updated with the new inventory from the PUT call
|
|
self.assertEqual(44, rp.generation)
|
|
expected = {
|
|
# Called with the newly-found generation from the existing
|
|
# inventory
|
|
'resource_provider_generation': 43,
|
|
'inventories': {
|
|
'VCPU': {
|
|
'total': 8,
|
|
'reserved': 0,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.vcpus,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.cpu_allocation_ratio,
|
|
},
|
|
'MEMORY_MB': {
|
|
'total': 1024,
|
|
'reserved': CONF.reserved_host_memory_mb,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.memory_mb,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.ram_allocation_ratio,
|
|
},
|
|
'DISK_GB': {
|
|
'total': 10,
|
|
'reserved': CONF.reserved_host_disk_mb * 1024,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.local_gb,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.disk_allocation_ratio,
|
|
},
|
|
}
|
|
}
|
|
mock_put.assert_called_once_with(exp_url, expected)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
def test_update_inventory_no_update(self, mock_put, mock_get):
|
|
uuid = uuids.compute_node
|
|
compute_node = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
|
|
self.client._resource_providers[uuid] = rp
|
|
mock_get.return_value.json.return_value = {
|
|
'resource_provider_generation': 43,
|
|
'inventories': {
|
|
'VCPU': {
|
|
'total': 8,
|
|
'reserved': 0,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.vcpus,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.cpu_allocation_ratio,
|
|
},
|
|
'MEMORY_MB': {
|
|
'total': 1024,
|
|
'reserved': CONF.reserved_host_memory_mb,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.memory_mb,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.ram_allocation_ratio,
|
|
},
|
|
'DISK_GB': {
|
|
'total': 10,
|
|
'reserved': CONF.reserved_host_disk_mb * 1024,
|
|
'min_unit': 1,
|
|
'max_unit': compute_node.local_gb,
|
|
'step_size': 1,
|
|
'allocation_ratio': compute_node.disk_allocation_ratio,
|
|
},
|
|
}
|
|
}
|
|
inv_data = report._compute_node_to_inventory_dict(compute_node)
|
|
result = self.client._update_inventory_attempt(
|
|
compute_node.uuid, inv_data
|
|
)
|
|
self.assertTrue(result)
|
|
exp_url = '/resource_providers/%s/inventories' % uuid
|
|
mock_get.assert_called_once_with(exp_url)
|
|
# No update so put should not be called
|
|
self.assertFalse(mock_put.called)
|
|
# Make sure we updated the generation from the inventory records
|
|
self.assertEqual(43, rp.generation)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_inventory')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_ensure_resource_provider')
|
|
def test_update_inventory_concurrent_update(self, mock_ensure,
|
|
mock_put, mock_get):
|
|
# Ensure _update_inventory() returns a list of Inventories objects
|
|
# after creating or updating the existing values
|
|
uuid = uuids.compute_node
|
|
compute_node = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[uuid] = rp
|
|
|
|
mock_get.return_value = {}
|
|
mock_put.return_value.status_code = 409
|
|
mock_put.return_value.text = 'Does not match inventory in use'
|
|
|
|
inv_data = report._compute_node_to_inventory_dict(compute_node)
|
|
result = self.client._update_inventory_attempt(
|
|
compute_node.uuid, inv_data
|
|
)
|
|
self.assertFalse(result)
|
|
|
|
# Invalidated the cache
|
|
self.assertNotIn(uuid, self.client._resource_providers)
|
|
# Refreshed our resource provider
|
|
mock_ensure.assert_called_once_with(uuid)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_inventory')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
def test_update_inventory_inventory_in_use(self, mock_put, mock_get):
|
|
# Ensure _update_inventory() returns a list of Inventories objects
|
|
# after creating or updating the existing values
|
|
uuid = uuids.compute_node
|
|
compute_node = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[uuid] = rp
|
|
|
|
mock_get.return_value = {}
|
|
mock_put.return_value.status_code = 409
|
|
mock_put.return_value.text = (
|
|
"update conflict: Inventory for VCPU on "
|
|
"resource provider 123 in use"
|
|
)
|
|
|
|
inv_data = report._compute_node_to_inventory_dict(compute_node)
|
|
self.assertRaises(
|
|
exception.InventoryInUse,
|
|
self.client._update_inventory_attempt,
|
|
compute_node.uuid,
|
|
inv_data,
|
|
)
|
|
|
|
# Did NOT invalidate the cache
|
|
self.assertIn(uuid, self.client._resource_providers)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_inventory')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
def test_update_inventory_unknown_response(self, mock_put, mock_get):
|
|
# Ensure _update_inventory() returns a list of Inventories objects
|
|
# after creating or updating the existing values
|
|
uuid = uuids.compute_node
|
|
compute_node = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[uuid] = rp
|
|
|
|
mock_get.return_value = {}
|
|
mock_put.return_value.status_code = 234
|
|
|
|
inv_data = report._compute_node_to_inventory_dict(compute_node)
|
|
result = self.client._update_inventory_attempt(
|
|
compute_node.uuid, inv_data
|
|
)
|
|
self.assertFalse(result)
|
|
|
|
# No cache invalidation
|
|
self.assertIn(uuid, self.client._resource_providers)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_get_inventory')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
def test_update_inventory_failed(self, mock_put, mock_get):
|
|
# Ensure _update_inventory() returns a list of Inventories objects
|
|
# after creating or updating the existing values
|
|
uuid = uuids.compute_node
|
|
compute_node = self.compute_node
|
|
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
|
|
# Make sure the ResourceProvider exists for preventing to call the API
|
|
self.client._resource_providers[uuid] = rp
|
|
|
|
mock_get.return_value = {}
|
|
try:
|
|
mock_put.return_value.__nonzero__.return_value = False
|
|
except AttributeError:
|
|
# Thanks py3
|
|
mock_put.return_value.__bool__.return_value = False
|
|
|
|
inv_data = report._compute_node_to_inventory_dict(compute_node)
|
|
result = self.client._update_inventory_attempt(
|
|
compute_node.uuid, inv_data
|
|
)
|
|
self.assertFalse(result)
|
|
|
|
# No cache invalidation
|
|
self.assertIn(uuid, self.client._resource_providers)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_ensure_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_update_inventory_attempt')
|
|
@mock.patch('time.sleep')
|
|
def test_update_inventory_fails_and_then_succeeds(self, mock_sleep,
|
|
mock_update,
|
|
mock_ensure):
|
|
# Ensure _update_inventory() fails if we have a conflict when updating
|
|
# but retries correctly.
|
|
cn = mock.MagicMock()
|
|
mock_update.side_effect = (False, True)
|
|
|
|
self.client._resource_providers[cn.uuid] = True
|
|
result = self.client._update_inventory(
|
|
cn.uuid, mock.sentinel.inv_data
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
# Only slept once
|
|
mock_sleep.assert_called_once_with(1)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_ensure_resource_provider')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'_update_inventory_attempt')
|
|
@mock.patch('time.sleep')
|
|
def test_update_inventory_never_succeeds(self, mock_sleep,
|
|
mock_update,
|
|
mock_ensure):
|
|
# but retries correctly.
|
|
cn = mock.MagicMock()
|
|
mock_update.side_effect = (False, False, False)
|
|
|
|
self.client._resource_providers[cn.uuid] = True
|
|
result = self.client._update_inventory(
|
|
cn.uuid, mock.sentinel.inv_data
|
|
)
|
|
self.assertFalse(result)
|
|
|
|
# Slept three times
|
|
mock_sleep.assert_has_calls([mock.call(1), mock.call(1), mock.call(1)])
|
|
|
|
# Three attempts to update
|
|
mock_update.assert_has_calls([
|
|
mock.call(cn.uuid, mock.sentinel.inv_data),
|
|
mock.call(cn.uuid, mock.sentinel.inv_data),
|
|
mock.call(cn.uuid, mock.sentinel.inv_data),
|
|
])
|
|
|
|
# Slept three times
|
|
mock_sleep.assert_has_calls([mock.call(1), mock.call(1), mock.call(1)])
|
|
|
|
|
|
class TestAllocations(SchedulerReportClientTestCase):
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance')
|
|
def test_instance_to_allocations_dict(self, mock_vbi):
|
|
mock_vbi.return_value = False
|
|
inst = objects.Instance(
|
|
uuid=uuids.inst,
|
|
flavor=objects.Flavor(root_gb=10,
|
|
swap=1023,
|
|
ephemeral_gb=100,
|
|
memory_mb=1024,
|
|
vcpus=2))
|
|
result = report._instance_to_allocations_dict(inst)
|
|
expected = {
|
|
'MEMORY_MB': 1024,
|
|
'VCPU': 2,
|
|
'DISK_GB': 111,
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance')
|
|
def test_instance_to_allocations_dict_boot_from_volume(self, mock_vbi):
|
|
mock_vbi.return_value = True
|
|
inst = objects.Instance(
|
|
uuid=uuids.inst,
|
|
flavor=objects.Flavor(root_gb=10,
|
|
swap=1,
|
|
ephemeral_gb=100,
|
|
memory_mb=1024,
|
|
vcpus=2))
|
|
result = report._instance_to_allocations_dict(inst)
|
|
expected = {
|
|
'MEMORY_MB': 1024,
|
|
'VCPU': 2,
|
|
'DISK_GB': 101,
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance')
|
|
def test_instance_to_allocations_dict_zero_disk(self, mock_vbi):
|
|
mock_vbi.return_value = True
|
|
inst = objects.Instance(
|
|
uuid=uuids.inst,
|
|
flavor=objects.Flavor(root_gb=10,
|
|
swap=0,
|
|
ephemeral_gb=0,
|
|
memory_mb=1024,
|
|
vcpus=2))
|
|
result = report._instance_to_allocations_dict(inst)
|
|
expected = {
|
|
'MEMORY_MB': 1024,
|
|
'VCPU': 2,
|
|
}
|
|
self.assertEqual(expected, result)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
@mock.patch('nova.scheduler.client.report.'
|
|
'_instance_to_allocations_dict')
|
|
def test_update_instance_allocation_new(self, mock_a, mock_get,
|
|
mock_put):
|
|
cn = objects.ComputeNode(uuid=uuids.cn)
|
|
inst = objects.Instance(uuid=uuids.inst)
|
|
mock_get.return_value.json.return_value = {'allocations': {}}
|
|
expected = {
|
|
'allocations': [
|
|
{'resource_provider': {'uuid': cn.uuid},
|
|
'resources': mock_a.return_value}]
|
|
}
|
|
self.client.update_instance_allocation(cn, inst, 1)
|
|
mock_put.assert_called_once_with(
|
|
'/allocations/%s' % inst.uuid,
|
|
expected)
|
|
self.assertTrue(mock_get.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
@mock.patch('nova.scheduler.client.report.'
|
|
'_instance_to_allocations_dict')
|
|
def test_update_instance_allocation_existing(self, mock_a, mock_get,
|
|
mock_put):
|
|
cn = objects.ComputeNode(uuid=uuids.cn)
|
|
inst = objects.Instance(uuid=uuids.inst)
|
|
mock_get.return_value.json.return_value = {'allocations': {
|
|
cn.uuid: {
|
|
'generation': 2,
|
|
'resources': {
|
|
'DISK_GB': 123,
|
|
'MEMORY_MB': 456,
|
|
}
|
|
}}
|
|
}
|
|
mock_a.return_value = {
|
|
'DISK_GB': 123,
|
|
'MEMORY_MB': 456,
|
|
}
|
|
self.client.update_instance_allocation(cn, inst, 1)
|
|
self.assertFalse(mock_put.called)
|
|
mock_get.assert_called_once_with(
|
|
'/allocations/%s' % inst.uuid)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'put')
|
|
@mock.patch('nova.scheduler.client.report.'
|
|
'_instance_to_allocations_dict')
|
|
@mock.patch.object(report.LOG, 'warning')
|
|
def test_update_instance_allocation_new_failed(self, mock_warn, mock_a,
|
|
mock_put, mock_get):
|
|
cn = objects.ComputeNode(uuid=uuids.cn)
|
|
inst = objects.Instance(uuid=uuids.inst)
|
|
try:
|
|
mock_put.return_value.__nonzero__.return_value = False
|
|
except AttributeError:
|
|
# NOTE(danms): LOL @ py3
|
|
mock_put.return_value.__bool__.return_value = False
|
|
self.client.update_instance_allocation(cn, inst, 1)
|
|
self.assertTrue(mock_warn.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'delete')
|
|
def test_update_instance_allocation_delete(self, mock_delete):
|
|
cn = objects.ComputeNode(uuid=uuids.cn)
|
|
inst = objects.Instance(uuid=uuids.inst)
|
|
self.client.update_instance_allocation(cn, inst, -1)
|
|
mock_delete.assert_called_once_with(
|
|
'/allocations/%s' % inst.uuid)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'delete')
|
|
@mock.patch.object(report.LOG, 'warning')
|
|
def test_update_instance_allocation_delete_failed(self, mock_warn,
|
|
mock_delete):
|
|
cn = objects.ComputeNode(uuid=uuids.cn)
|
|
inst = objects.Instance(uuid=uuids.inst)
|
|
try:
|
|
mock_delete.return_value.__nonzero__.return_value = False
|
|
except AttributeError:
|
|
# NOTE(danms): LOL @ py3
|
|
mock_delete.return_value.__bool__.return_value = False
|
|
self.client.update_instance_allocation(cn, inst, -1)
|
|
self.assertTrue(mock_warn.called)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'delete')
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'get')
|
|
@mock.patch('nova.scheduler.client.report.'
|
|
'_instance_to_allocations_dict')
|
|
def test_remove_deleted_instances(self, mock_a, mock_get,
|
|
mock_delete):
|
|
cn = objects.ComputeNode(uuid=uuids.cn)
|
|
inst1 = objects.Instance(uuid=uuids.inst1)
|
|
inst2 = objects.Instance(uuid=uuids.inst2)
|
|
fake_allocations = {
|
|
'MEMORY_MB': 1024,
|
|
'VCPU': 2,
|
|
'DISK_GB': 101,
|
|
}
|
|
mock_get.return_value.json.return_value = {'allocations': {
|
|
inst1.uuid: fake_allocations,
|
|
inst2.uuid: fake_allocations,
|
|
}
|
|
}
|
|
|
|
# One instance still on the node, dict form as the
|
|
# RT tracks it
|
|
inst3 = {'uuid': 'foo'}
|
|
|
|
mock_delete.return_value = True
|
|
self.client.remove_deleted_instances(cn, [inst3])
|
|
mock_get.assert_called_once_with(
|
|
'/resource_providers/%s/allocations' % cn.uuid)
|
|
expected_calls = [
|
|
mock.call('/allocations/%s' % inst1.uuid),
|
|
mock.call('/allocations/%s' % inst2.uuid)]
|
|
mock_delete.assert_has_calls(expected_calls, any_order=True)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
|
|
'delete')
|
|
@mock.patch('nova.scheduler.client.report.LOG')
|
|
def test_delete_allocation_for_instance_ignore_404(self, mock_log,
|
|
mock_delete):
|
|
"""Tests that we don't log a warning on a 404 response when trying to
|
|
delete an allocation record.
|
|
"""
|
|
mock_response = mock.MagicMock(status_code=404)
|
|
try:
|
|
mock_response.__nonzero__.return_value = False
|
|
except AttributeError:
|
|
# py3 uses __bool__
|
|
mock_response.__bool__.return_value = False
|
|
mock_delete.return_value = mock_response
|
|
self.client._delete_allocation_for_instance(uuids.rp_uuid)
|
|
# make sure we didn't screw up the logic or the mock
|
|
mock_log.info.assert_not_called()
|
|
# make sure warning wasn't called for the 404
|
|
mock_log.warning.assert_not_called()
|
|
|
|
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
|
|
"delete")
|
|
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
|
|
"_delete_allocation_for_instance")
|
|
@mock.patch("nova.objects.InstanceList.get_by_host_and_node")
|
|
def test_delete_resource_provider_cascade(self, mock_by_host,
|
|
mock_del_alloc, mock_delete):
|
|
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
|
|
hypervisor_hostname="fake_hostname", )
|
|
inst1 = objects.Instance(uuid=uuids.inst1)
|
|
inst2 = objects.Instance(uuid=uuids.inst2)
|
|
mock_by_host.return_value = objects.InstanceList(
|
|
objects=[inst1, inst2])
|
|
resp_mock = mock.Mock(status_code=204)
|
|
mock_delete.return_value = resp_mock
|
|
self.client.delete_resource_provider(self.context, cn, cascade=True)
|
|
self.assertEqual(2, mock_del_alloc.call_count)
|
|
exp_url = "/resource_providers/%s" % uuids.cn
|
|
mock_delete.assert_called_once_with(exp_url)
|
|
|
|
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
|
|
"delete")
|
|
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
|
|
"_delete_allocation_for_instance")
|
|
@mock.patch("nova.objects.InstanceList.get_by_host_and_node")
|
|
def test_delete_resource_provider_no_cascade(self, mock_by_host,
|
|
mock_del_alloc, mock_delete):
|
|
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
|
|
hypervisor_hostname="fake_hostname", )
|
|
inst1 = objects.Instance(uuid=uuids.inst1)
|
|
inst2 = objects.Instance(uuid=uuids.inst2)
|
|
mock_by_host.return_value = objects.InstanceList(
|
|
objects=[inst1, inst2])
|
|
resp_mock = mock.Mock(status_code=204)
|
|
mock_delete.return_value = resp_mock
|
|
self.client.delete_resource_provider(self.context, cn)
|
|
mock_del_alloc.assert_not_called()
|
|
exp_url = "/resource_providers/%s" % uuids.cn
|
|
mock_delete.assert_called_once_with(exp_url)
|
|
|
|
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
|
|
"delete")
|
|
@mock.patch('nova.scheduler.client.report.LOG')
|
|
def test_delete_resource_provider_log_calls(self, mock_log, mock_delete):
|
|
# First, check a successful call
|
|
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
|
|
hypervisor_hostname="fake_hostname", )
|
|
resp_mock = mock.MagicMock(status_code=204)
|
|
try:
|
|
resp_mock.__nonzero__.return_value = True
|
|
except AttributeError:
|
|
# py3 uses __bool__
|
|
resp_mock.__bool__.return_value = True
|
|
mock_delete.return_value = resp_mock
|
|
self.client.delete_resource_provider(self.context, cn)
|
|
# With a 204, only the info should be called
|
|
self.assertEqual(1, mock_log.info.call_count)
|
|
self.assertEqual(0, mock_log.warning.call_count)
|
|
|
|
# Now check a 404 response
|
|
mock_log.reset_mock()
|
|
resp_mock.status_code = 404
|
|
try:
|
|
resp_mock.__nonzero__.return_value = False
|
|
except AttributeError:
|
|
# py3 uses __bool__
|
|
resp_mock.__bool__.return_value = False
|
|
self.client.delete_resource_provider(self.context, cn)
|
|
# With a 404, neither log message should be called
|
|
self.assertEqual(0, mock_log.info.call_count)
|
|
self.assertEqual(0, mock_log.warning.call_count)
|
|
|
|
# Finally, check a 409 response
|
|
mock_log.reset_mock()
|
|
resp_mock.status_code = 409
|
|
self.client.delete_resource_provider(self.context, cn)
|
|
# With a 409, only the warning should be called
|
|
self.assertEqual(0, mock_log.info.call_count)
|
|
self.assertEqual(1, mock_log.warning.call_count)
|