From 0484f9710b66f736860bb2eb859617e656e58361 Mon Sep 17 00:00:00 2001 From: He Yongli Date: Thu, 1 Aug 2013 17:25:45 +0800 Subject: [PATCH] Add scheduler support for PCI passthrough PCI tracker updates the PCI stats information which provide an abstract resource view to the scheduler Openstack clients request PCI devices using PCI aliases, which are converted to PCI requests. The PCI passthrough filter checks the PCI stats for nodes that can satisfy the request of the instance, and gives the schedule result. bp:pci-passthrough-base Change-Id: I0ede4caefab8b22126d0c8b9293f023aa4780e36 Signed-off-by: Yongli He Signed-off-by: Yunhong Jiang --- nova/scheduler/filter_scheduler.py | 5 +++ .../filters/pci_passthrough_filter.py | 42 +++++++++++++++++++ nova/scheduler/host_manager.py | 10 +++++ nova/tests/scheduler/test_filter_scheduler.py | 35 +++++++++++++--- nova/tests/scheduler/test_host_filters.py | 37 ++++++++++++++++ nova/tests/scheduler/test_host_manager.py | 21 ++++++++++ 6 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 nova/scheduler/filters/pci_passthrough_filter.py diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index cb91951ec..f9c15c106 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 000000000..322e41b6a --- /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 fcc876f9a..ce6238a76 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 de4bf584e..b692098d1 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 dd98ae127..f3b4f211f 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 af120aee4..8da2b1433 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")