summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/db/api.py9
-rw-r--r--nova/db/sqlalchemy/api.py6
-rw-r--r--nova/scheduler/filters/type_filter.py39
-rw-r--r--nova/tests/scheduler/test_host_filters.py29
4 files changed, 80 insertions, 3 deletions
diff --git a/nova/db/api.py b/nova/db/api.py
index f2f74cc55..ff72aff37 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -571,15 +571,20 @@ def instance_get_active_by_window_joined(context, begin, end=None,
def instance_get_all_by_project(context, project_id):
- """Get all instance belonging to a project."""
+ """Get all instances belonging to a project."""
return IMPL.instance_get_all_by_project(context, project_id)
def instance_get_all_by_host(context, host):
- """Get all instance belonging to a host."""
+ """Get all instances belonging to a host."""
return IMPL.instance_get_all_by_host(context, host)
+def instance_get_all_by_host_and_not_type(context, host, type_id=None):
+ """Get all instances belonging to a host with a different type_id."""
+ return IMPL.instance_get_all_by_host_and_not_type(context, host, type_id)
+
+
def instance_get_all_by_reservation(context, reservation_id):
"""Get all instances belonging to a reservation."""
return IMPL.instance_get_all_by_reservation(context, reservation_id)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 56a25ad71..88ddeac34 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -1548,6 +1548,12 @@ def instance_get_all_by_host(context, host):
return _instance_get_all_query(context).filter_by(host=host).all()
+@require_admin_context
+def instance_get_all_by_host_and_not_type(context, host, type_id=None):
+ return _instance_get_all_query(context).filter_by(host=host).\
+ filter(models.Instance.instance_type_id != type_id).all()
+
+
@require_context
def instance_get_all_by_project(context, project_id):
authorize_project_context(context, project_id)
diff --git a/nova/scheduler/filters/type_filter.py b/nova/scheduler/filters/type_filter.py
new file mode 100644
index 000000000..a8d842d52
--- /dev/null
+++ b/nova/scheduler/filters/type_filter.py
@@ -0,0 +1,39 @@
+# Copyright (c) 2012 The Cloudscaling Group, Inc.
+#
+# 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 import db
+from nova.scheduler import filters
+
+
+class TypeAffinityFilter(filters.BaseHostFilter):
+ """TypeAffinityFilter doesn't allow more then one VM type per host.
+
+ Note: this works best with compute_fill_first_cost_fn_weight
+ (dispersion) set to 1 (-1 by default).
+ """
+
+ def host_passes(self, host_state, filter_properties):
+ """Dynamically limits hosts to one instance type
+
+ Return False if host has any instance types other then the requested
+ type. Return True if all instance types match or if host is empty.
+ """
+
+ instance_type = filter_properties.get('instance_type')
+ context = filter_properties['context'].elevated()
+ instances_other_type = db.instance_get_all_by_host_and_not_type(
+ context, host_state.host, instance_type['id'])
+ return len(instances_other_type) == 0
diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py
index 5659d9467..22a162aa2 100644
--- a/nova/tests/scheduler/test_host_filters.py
+++ b/nova/tests/scheduler/test_host_filters.py
@@ -45,7 +45,7 @@ class HostFiltersTestCase(test.TestCase):
['and', ['>=', '$free_ram_mb', 1024],
['>=', '$free_disk_mb', 200 * 1024]])
# This has a side effect of testing 'get_filter_classes'
- # when specifing a method (in this case, our standard filters)
+ # when specifying a method (in this case, our standard filters)
classes = filters.get_filter_classes(
['nova.scheduler.filters.standard_filters'])
self.class_map = {}
@@ -174,6 +174,33 @@ class HostFiltersTestCase(test.TestCase):
'service': service})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
+ def test_type_filter(self):
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['TypeAffinityFilter']()
+
+ filter_properties = {'context': self.context,
+ 'instance_type': {'id': 1}}
+ filter2_properties = {'context': self.context,
+ 'instance_type': {'id': 2}}
+
+ capabilities = {'enabled': True}
+ service = {'disabled': False}
+ host = fakes.FakeHostState('fake_host', 'compute',
+ {'capabilities': capabilities,
+ 'service': service})
+ #True since empty
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+ fakes.FakeInstance(context=self.context,
+ params={'host': 'fake_host', 'instance_type_id': 1})
+ #True since same type
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+ #False since different type
+ self.assertFalse(filt_cls.host_passes(host, filter2_properties))
+ #False since node not homogeneous
+ fakes.FakeInstance(context=self.context,
+ params={'host': 'fake_host', 'instance_type_id': 2})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
def test_ram_filter_fails_on_memory(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['RamFilter']()