diff --git a/nova/tests/functional/api/openstack/placement/test_report_client.py b/nova/tests/functional/api/openstack/placement/test_report_client.py new file mode 100644 index 000000000000..1ccb97146684 --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/test_report_client.py @@ -0,0 +1,142 @@ +# +# 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 session +import mock +import requests +from wsgi_intercept import interceptor + +from nova.api.openstack.placement import deploy +from nova import conf +from nova import objects +from nova.objects import fields +from nova.scheduler.client import report +from nova import test +from nova.tests import uuidsentinel as uuids + +CONF = conf.CONF + + +class NoAuthReportClient(report.SchedulerReportClient): + """A SchedulerReportClient that avoids keystone.""" + + def __init__(self): + self._resource_providers = {} + self._disabled = False + # Supply our own session so the wsgi-intercept can intercept + # the right thing. Another option would be to use the direct + # urllib3 interceptor. + request_session = requests.Session() + self._client = session.Session( + auth=None, + session=request_session, + additional_headers={'x-auth-token': 'admin'}) + + +class SchedulerReportClientTests(test.TestCase): + """Set up an intercepted placement API to test against.""" + + def setUp(self): + super(SchedulerReportClientTests, self).setUp() + self.flags(auth_strategy='noauth2') + + self.app = lambda: deploy.loadapp(CONF) + self.client = NoAuthReportClient() + # TODO(cdent): Port required here to deal with a bug + # in wsgi-intercept: + # https://github.com/cdent/wsgi-intercept/issues/41 + self.url = 'http://localhost:80/placement' + self.compute_uuid = uuids.compute_node + self.compute_name = 'computehost' + self.compute_node = objects.ComputeNode( + uuid=self.compute_uuid, + hypervisor_hostname=self.compute_name, + vcpus=2, + cpu_allocation_ratio=16.0, + memory_mb=2048, + ram_allocation_ratio=1.5, + local_gb=1024, + disk_allocation_ratio=1.0) + + self.instance_uuid = uuids.inst + self.instance = objects.Instance( + uuid=self.instance_uuid, + flavor=objects.Flavor(root_gb=10, + swap=1, + ephemeral_gb=100, + memory_mb=1024, + vcpus=2)) + + @mock.patch('nova.compute.utils.is_volume_backed_instance', + return_value=False) + @mock.patch('nova.objects.compute_node.ComputeNode.save') + @mock.patch('keystoneauth1.session.Session.get_auth_headers', + return_value={'x-auth-token': 'admin'}) + @mock.patch('keystoneauth1.session.Session.get_endpoint', + return_value='http://localhost:80/placement') + def test_client_report_smoke(self, mock_vbi, mock_endpoint, mock_auth, + mock_cn): + """Check things go as expected when doing the right things.""" + # TODO(cdent): We should probably also have a test that + # tests that when allocation or inventory errors happen, we + # are resilient. + res_class = fields.ResourceClass.VCPU + with interceptor.RequestsInterceptor( + app=self.app, url=self.url): + # When we start out there are no resource providers. + rp = self.client._get_resource_provider(self.compute_uuid) + self.assertIsNone(rp) + + # Now let's update status for our compute node. + self.client.update_resource_stats(self.compute_node) + + # So now we have a resource provider + rp = self.client._get_resource_provider(self.compute_uuid) + self.assertIsNotNone(rp) + + # TODO(cdent): change this to use the methods built in + # to the report client to retrieve inventory? + inventory_url = ('/resource_providers/%s/inventories' % + self.compute_uuid) + resp = self.client.get(inventory_url) + inventory_data = resp.json()['inventories'] + self.assertEqual(self.compute_node.vcpus, + inventory_data[res_class]['total']) + + # Update allocations with our instance + self.client.update_instance_allocation( + self.compute_node, self.instance, 1) + + # Check that allocations were made + resp = self.client.get('/allocations/%s' % self.instance_uuid) + alloc_data = resp.json()['allocations'] + vcpu_data = alloc_data[self.compute_uuid]['resources'][res_class] + self.assertEqual(2, vcpu_data) + + # Check that usages are up to date + resp = self.client.get('/resource_providers/%s/usages' % + self.compute_uuid) + usage_data = resp.json()['usages'] + vcpu_data = usage_data[res_class] + self.assertEqual(2, vcpu_data) + + # Delete allocations with our instance + self.client.update_instance_allocation( + self.compute_node, self.instance, -1) + + # No usage + resp = self.client.get('/resource_providers/%s/usages' % + self.compute_uuid) + usage_data = resp.json()['usages'] + vcpu_data = usage_data[res_class] + self.assertEqual(0, vcpu_data) diff --git a/requirements.txt b/requirements.txt index 63c0906eff31..483a308ce563 100644 --- a/requirements.txt +++ b/requirements.txt @@ -58,3 +58,4 @@ os-vif>=1.1.0 # Apache-2.0 os-win>=0.2.3 # Apache-2.0 castellan>=0.4.0 # Apache-2.0 microversion-parse>=0.1.2 # Apache-2.0 +wsgi_intercept>=0.6.1 # MIT License