diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index cb91951ec305..f9c15c106a92 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -30,6 +30,7 @@ from nova import exception from nova.openstack.common.gettextutils import _ from nova.openstack.common import log as logging from nova.openstack.common.notifier import api as notifier +from nova.pci import pci_request from nova.scheduler import driver from nova.scheduler import scheduler_options from nova.scheduler import utils as scheduler_utils @@ -212,6 +213,10 @@ class FilterScheduler(driver.Scheduler): os_type = request_spec['instance_properties']['os_type'] filter_properties['project_id'] = project_id filter_properties['os_type'] = os_type + pci_requests = pci_request.get_pci_requests_from_flavor( + request_spec.get('instance_type') or {}) + if pci_requests: + filter_properties['pci_requests'] = pci_requests def _max_attempts(self): max_attempts = CONF.scheduler_max_attempts diff --git a/nova/scheduler/filters/pci_passthrough_filter.py b/nova/scheduler/filters/pci_passthrough_filter.py new file mode 100644 index 000000000000..322e41b6a06c --- /dev/null +++ b/nova/scheduler/filters/pci_passthrough_filter.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2013 ISP RAS. +# 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. + +from nova.scheduler import filters + + +class PciPassthroughFilter(filters.BaseHostFilter): + """Pci Passthrough Filter based on PCI request + + Filter that schedules tasks on a host if the host has devices + to meet the device requests in the 'extra_specs' for the flavor. + + PCI resource tracker provides updated summary information about the + PCI devices for each host, like: + [{"count": 5, "vendor_id": "8086", "product_id": "1520", + "extra_info":'{}'}], + and VM requests PCI devices via PCI requests, like: + [{"count": 1, "vendor_id": "8086", "product_id": "1520",}]. + + The filter checkes if the host passes or not based on these information. + """ + + def host_passes(self, host_state, filter_properties): + """Return true if the host has the required PCI devices.""" + if not filter_properties.get('pci_requests'): + return True + return host_state.pci_stats.support_requests( + filter_properties.get('pci_requests')) diff --git a/nova/scheduler/host_manager.py b/nova/scheduler/host_manager.py index fcc876f9aa4a..ce6238a7657f 100644 --- a/nova/scheduler/host_manager.py +++ b/nova/scheduler/host_manager.py @@ -29,6 +29,8 @@ from nova.openstack.common.gettextutils import _ from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova.openstack.common import timeutils +from nova.pci import pci_request +from nova.pci import pci_stats from nova.scheduler import filters from nova.scheduler import weights @@ -166,6 +168,10 @@ class HostState(object): self.vcpus_total = compute['vcpus'] self.vcpus_used = compute['vcpus_used'] self.updated = compute['updated_at'] + if hasattr(compute, 'pci_stats'): + self.pci_stats = pci_stats.PciDeviceStats(compute['pci_stats']) + else: + self.pci_stats = None # All virt drivers report host_ip self.host_ip = compute['host_ip'] @@ -252,6 +258,10 @@ class HostState(object): self.num_instances_by_os_type[os_type] = 0 self.num_instances_by_os_type[os_type] += 1 + pci_requests = pci_request.get_instance_pci_requests(instance) + if pci_requests and self.pci_stats: + self.pci_stats.apply_requests(pci_requests) + vm_state = instance.get('vm_state', vm_states.BUILDING) task_state = instance.get('task_state') if vm_state == vm_states.BUILDING or task_state in [ diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index de4bf584eaa9..b692098d1738 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -25,6 +25,7 @@ from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception +from nova.pci import pci_request from nova.scheduler import driver from nova.scheduler import filter_scheduler from nova.scheduler import host_manager @@ -216,7 +217,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): sched = fakes.FakeFilterScheduler() instance_properties = {'project_id': '12345', 'os_type': 'Linux'} - request_spec = dict(instance_properties=instance_properties) + request_spec = dict(instance_properties=instance_properties, + instance_type={}) filter_properties = {} self.mox.StubOutWithMock(db, 'compute_node_get_all') @@ -273,7 +275,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): sched = fakes.FakeFilterScheduler() instance_properties = {'project_id': '12345', 'os_type': 'Linux'} - request_spec = dict(instance_properties=instance_properties) + request_spec = dict(instance_properties=instance_properties, + instance_type={}) filter_properties = {} self.mox.StubOutWithMock(db, 'compute_node_get_all') @@ -292,7 +295,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): sched = fakes.FakeFilterScheduler() instance_properties = {'project_id': '12345', 'os_type': 'Linux'} - request_spec = dict(instance_properties=instance_properties) + request_spec = dict(instance_properties=instance_properties, + instance_type={}) retry = dict(num_attempts=1) filter_properties = dict(retry=retry) @@ -443,7 +447,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): 'vcpus': 1, 'os_type': 'Linux'} - request_spec = dict(instance_properties=instance_properties) + request_spec = dict(instance_properties=instance_properties, + instance_type={}) filter_properties = {} self.mox.ReplayAll() hosts = sched._schedule(self.context, request_spec, @@ -472,7 +477,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): 'ephemeral_gb': 0, 'vcpus': 1, 'os_type': 'Linux'} - request_spec = dict(instance_properties=instance_properties) + request_spec = dict(instance_properties=instance_properties, + instance_type={}) filter_properties = {} self.mox.ReplayAll() hosts = sched._schedule(self.context, request_spec, @@ -511,7 +517,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): 'vcpus': 1, 'os_type': 'Linux'} - request_spec = dict(instance_properties=instance_properties) + request_spec = dict(instance_properties=instance_properties, + instance_type={}) self.stubs.Set(weights.HostWeightHandler, 'get_weighed_objects', _fake_weigh_objects) @@ -656,3 +663,19 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): sched._provision_resource(fake_context, weighted_host, request_spec, filter_properties, None, None, None, None) + + def test_pci_request_in_filter_properties(self): + instance_type = {} + request_spec = {'instance_type': instance_type, + 'instance_properties': {'project_id': 1, + 'os_type': 'Linux'}} + filter_properties = {} + requests = [{'count': 1, 'spec': [{'vendor_id': '8086'}]}] + self.mox.StubOutWithMock(pci_request, 'get_pci_requests_from_flavor') + pci_request.get_pci_requests_from_flavor( + instance_type).AndReturn(requests) + self.mox.ReplayAll() + self.driver.populate_filter_properties( + request_spec, filter_properties) + self.assertEqual(filter_properties.get('pci_requests'), + requests) diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index dd98ae12787d..f3b4f211fe35 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -24,6 +24,7 @@ from nova import context from nova import db from nova.openstack.common import jsonutils from nova.openstack.common import timeutils +from nova.pci import pci_stats from nova.scheduler import filters from nova.scheduler.filters import extra_specs_ops from nova.scheduler.filters import trusted_filter @@ -1522,3 +1523,39 @@ class HostFiltersTestCase(test.NoDBTestCase): 'project_id': 'my_tenantid'}}} host = fakes.FakeHostState('host1', 'compute', {}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) + + def _fake_pci_support_requests(self, pci_requests): + self.pci_requests = pci_requests + return self.pci_request_result + + def test_pci_passthrough_pass(self): + filt_cls = self.class_map['PciPassthroughFilter']() + requests = [{'count': 1, 'spec': [{'vendor_id': '8086'}]}] + filter_properties = {'pci_requests': requests} + self.stubs.Set(pci_stats.PciDeviceStats, 'support_requests', + self._fake_pci_support_requests) + host = fakes.FakeHostState( + 'host1', 'node1', + attribute_dict={'pci_stats': pci_stats.PciDeviceStats()}) + self.pci_request_result = True + self.assertTrue(filt_cls.host_passes(host, filter_properties)) + self.assertEqual(self.pci_requests, requests) + + def test_pci_passthrough_fail(self): + filt_cls = self.class_map['PciPassthroughFilter']() + requests = [{'count': 1, 'spec': [{'vendor_id': '8086'}]}] + filter_properties = {'pci_requests': requests} + self.stubs.Set(pci_stats.PciDeviceStats, 'support_requests', + self._fake_pci_support_requests) + host = fakes.FakeHostState( + 'host1', 'node1', + attribute_dict={'pci_stats': pci_stats.PciDeviceStats()}) + self.pci_request_result = False + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + self.assertEqual(self.pci_requests, requests) + + def test_pci_passthrough_no_pci_request(self): + filt_cls = self.class_map['PciPassthroughFilter']() + filter_properties = {} + host = fakes.FakeHostState('h1', 'n1', {}) + self.assertTrue(filt_cls.host_passes(host, filter_properties)) diff --git a/nova/tests/scheduler/test_host_manager.py b/nova/tests/scheduler/test_host_manager.py index af120aee45a0..8da2b1433c1d 100644 --- a/nova/tests/scheduler/test_host_manager.py +++ b/nova/tests/scheduler/test_host_manager.py @@ -464,6 +464,27 @@ class HostStateTestCase(test.NoDBTestCase): self.assertEqual('cpu_info', host.cpu_info) self.assertEqual({}, host.supported_instances) + def test_stat_consumption_from_compute_node_non_pci(self): + stats = [ + dict(key='num_instances', value='5'), + dict(key='num_proj_12345', value='3'), + dict(key='num_proj_23456', value='1'), + dict(key='num_vm_%s' % vm_states.BUILDING, value='2'), + dict(key='num_vm_%s' % vm_states.SUSPENDED, value='1'), + dict(key='num_task_%s' % task_states.RESIZE_MIGRATING, value='1'), + dict(key='num_task_%s' % task_states.MIGRATING, value='2'), + dict(key='num_os_type_linux', value='4'), + dict(key='num_os_type_windoze', value='1'), + dict(key='io_workload', value='42'), + ] + compute = dict(stats=stats, memory_mb=0, free_disk_gb=0, local_gb=0, + local_gb_used=0, free_ram_mb=0, vcpus=0, vcpus_used=0, + updated_at=None, host_ip='127.0.0.1') + + host = host_manager.HostState("fakehost", "fakenode") + host.update_from_compute_node(compute) + self.assertEqual(None, host.pci_stats) + def test_stat_consumption_from_instance(self): host = host_manager.HostState("fakehost", "fakenode")