Merge "Add scheduler support for PCI passthrough"
This commit is contained in:
@@ -30,6 +30,7 @@ from nova import exception
|
|||||||
from nova.openstack.common.gettextutils import _
|
from nova.openstack.common.gettextutils import _
|
||||||
from nova.openstack.common import log as logging
|
from nova.openstack.common import log as logging
|
||||||
from nova.openstack.common.notifier import api as notifier
|
from nova.openstack.common.notifier import api as notifier
|
||||||
|
from nova.pci import pci_request
|
||||||
from nova.scheduler import driver
|
from nova.scheduler import driver
|
||||||
from nova.scheduler import scheduler_options
|
from nova.scheduler import scheduler_options
|
||||||
from nova.scheduler import utils as scheduler_utils
|
from nova.scheduler import utils as scheduler_utils
|
||||||
@@ -212,6 +213,10 @@ class FilterScheduler(driver.Scheduler):
|
|||||||
os_type = request_spec['instance_properties']['os_type']
|
os_type = request_spec['instance_properties']['os_type']
|
||||||
filter_properties['project_id'] = project_id
|
filter_properties['project_id'] = project_id
|
||||||
filter_properties['os_type'] = os_type
|
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):
|
def _max_attempts(self):
|
||||||
max_attempts = CONF.scheduler_max_attempts
|
max_attempts = CONF.scheduler_max_attempts
|
||||||
|
42
nova/scheduler/filters/pci_passthrough_filter.py
Normal file
42
nova/scheduler/filters/pci_passthrough_filter.py
Normal file
@@ -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'))
|
@@ -29,6 +29,8 @@ from nova.openstack.common.gettextutils import _
|
|||||||
from nova.openstack.common import jsonutils
|
from nova.openstack.common import jsonutils
|
||||||
from nova.openstack.common import log as logging
|
from nova.openstack.common import log as logging
|
||||||
from nova.openstack.common import timeutils
|
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 filters
|
||||||
from nova.scheduler import weights
|
from nova.scheduler import weights
|
||||||
|
|
||||||
@@ -166,6 +168,10 @@ class HostState(object):
|
|||||||
self.vcpus_total = compute['vcpus']
|
self.vcpus_total = compute['vcpus']
|
||||||
self.vcpus_used = compute['vcpus_used']
|
self.vcpus_used = compute['vcpus_used']
|
||||||
self.updated = compute['updated_at']
|
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
|
# All virt drivers report host_ip
|
||||||
self.host_ip = compute['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] = 0
|
||||||
self.num_instances_by_os_type[os_type] += 1
|
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)
|
vm_state = instance.get('vm_state', vm_states.BUILDING)
|
||||||
task_state = instance.get('task_state')
|
task_state = instance.get('task_state')
|
||||||
if vm_state == vm_states.BUILDING or task_state in [
|
if vm_state == vm_states.BUILDING or task_state in [
|
||||||
|
@@ -25,6 +25,7 @@ from nova.conductor import api as conductor_api
|
|||||||
from nova import context
|
from nova import context
|
||||||
from nova import db
|
from nova import db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
from nova.pci import pci_request
|
||||||
from nova.scheduler import driver
|
from nova.scheduler import driver
|
||||||
from nova.scheduler import filter_scheduler
|
from nova.scheduler import filter_scheduler
|
||||||
from nova.scheduler import host_manager
|
from nova.scheduler import host_manager
|
||||||
@@ -216,7 +217,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
|
|
||||||
instance_properties = {'project_id': '12345', 'os_type': 'Linux'}
|
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 = {}
|
filter_properties = {}
|
||||||
|
|
||||||
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
||||||
@@ -273,7 +275,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
|
|
||||||
instance_properties = {'project_id': '12345', 'os_type': 'Linux'}
|
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 = {}
|
filter_properties = {}
|
||||||
|
|
||||||
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
||||||
@@ -292,7 +295,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
|
|
||||||
instance_properties = {'project_id': '12345', 'os_type': 'Linux'}
|
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)
|
retry = dict(num_attempts=1)
|
||||||
filter_properties = dict(retry=retry)
|
filter_properties = dict(retry=retry)
|
||||||
@@ -443,7 +447,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
'vcpus': 1,
|
'vcpus': 1,
|
||||||
'os_type': 'Linux'}
|
'os_type': 'Linux'}
|
||||||
|
|
||||||
request_spec = dict(instance_properties=instance_properties)
|
request_spec = dict(instance_properties=instance_properties,
|
||||||
|
instance_type={})
|
||||||
filter_properties = {}
|
filter_properties = {}
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
hosts = sched._schedule(self.context, request_spec,
|
hosts = sched._schedule(self.context, request_spec,
|
||||||
@@ -472,7 +477,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
'ephemeral_gb': 0,
|
'ephemeral_gb': 0,
|
||||||
'vcpus': 1,
|
'vcpus': 1,
|
||||||
'os_type': 'Linux'}
|
'os_type': 'Linux'}
|
||||||
request_spec = dict(instance_properties=instance_properties)
|
request_spec = dict(instance_properties=instance_properties,
|
||||||
|
instance_type={})
|
||||||
filter_properties = {}
|
filter_properties = {}
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
hosts = sched._schedule(self.context, request_spec,
|
hosts = sched._schedule(self.context, request_spec,
|
||||||
@@ -511,7 +517,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
'vcpus': 1,
|
'vcpus': 1,
|
||||||
'os_type': 'Linux'}
|
'os_type': 'Linux'}
|
||||||
|
|
||||||
request_spec = dict(instance_properties=instance_properties)
|
request_spec = dict(instance_properties=instance_properties,
|
||||||
|
instance_type={})
|
||||||
|
|
||||||
self.stubs.Set(weights.HostWeightHandler,
|
self.stubs.Set(weights.HostWeightHandler,
|
||||||
'get_weighed_objects', _fake_weigh_objects)
|
'get_weighed_objects', _fake_weigh_objects)
|
||||||
@@ -656,3 +663,19 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
sched._provision_resource(fake_context, weighted_host,
|
sched._provision_resource(fake_context, weighted_host,
|
||||||
request_spec, filter_properties,
|
request_spec, filter_properties,
|
||||||
None, None, None, None)
|
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)
|
||||||
|
@@ -24,6 +24,7 @@ from nova import context
|
|||||||
from nova import db
|
from nova import db
|
||||||
from nova.openstack.common import jsonutils
|
from nova.openstack.common import jsonutils
|
||||||
from nova.openstack.common import timeutils
|
from nova.openstack.common import timeutils
|
||||||
|
from nova.pci import pci_stats
|
||||||
from nova.scheduler import filters
|
from nova.scheduler import filters
|
||||||
from nova.scheduler.filters import extra_specs_ops
|
from nova.scheduler.filters import extra_specs_ops
|
||||||
from nova.scheduler.filters import trusted_filter
|
from nova.scheduler.filters import trusted_filter
|
||||||
@@ -1537,3 +1538,39 @@ class HostFiltersTestCase(test.NoDBTestCase):
|
|||||||
'project_id': 'my_tenantid'}}}
|
'project_id': 'my_tenantid'}}}
|
||||||
host = fakes.FakeHostState('host1', 'compute', {})
|
host = fakes.FakeHostState('host1', 'compute', {})
|
||||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
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))
|
||||||
|
@@ -464,6 +464,27 @@ class HostStateTestCase(test.NoDBTestCase):
|
|||||||
self.assertEqual('cpu_info', host.cpu_info)
|
self.assertEqual('cpu_info', host.cpu_info)
|
||||||
self.assertEqual({}, host.supported_instances)
|
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):
|
def test_stat_consumption_from_instance(self):
|
||||||
host = host_manager.HostState("fakehost", "fakenode")
|
host = host_manager.HostState("fakehost", "fakenode")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user