General host aggregates part 2
Partially implements blueprint general-host-aggregates: Scheduler Filter * Add AggregateInstanceExtraSpecsFilter * change db.aggregate_get_by_host to return a list of aggregates instead of the first aggregate * Add optional key filter to db.aggregate_get_by_host along with test * Add db.aggregate_metadata_get_by_host to get host aggregate metadata * add optional key filter to db.aggregate_metadata_get_by_host * Add AggregateTypeAffinityFilter Change-Id: I4a3061276cc3b0c5c695eaf973b773183b3c4f4b
This commit is contained in:
parent
55f627fe07
commit
7d48c9fb40
51
nova/scheduler/filters/aggregate_instance_extra_specs.py
Normal file
51
nova/scheduler/filters/aggregate_instance_extra_specs.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Copyright (c) 2012 OpenStack, LLC.
|
||||
# Copyright (c) 2012 Cloudscaling
|
||||
# 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 import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.scheduler import filters
|
||||
from nova import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AggregateInstanceExtraSpecsFilter(filters.BaseHostFilter):
|
||||
"""AggregateInstanceExtraSpecsFilter works with InstanceType records."""
|
||||
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
"""Return a list of hosts that can create instance_type
|
||||
|
||||
Check that the extra specs associated with the instance type match
|
||||
the metadata provided by aggregates. If not present return False.
|
||||
"""
|
||||
instance_type = filter_properties.get('instance_type')
|
||||
if 'extra_specs' not in instance_type:
|
||||
return True
|
||||
|
||||
context = filter_properties['context'].elevated()
|
||||
metadata = db.aggregate_metadata_get_by_host(context, host_state.host)
|
||||
|
||||
for key, value in instance_type['extra_specs'].iteritems():
|
||||
aggregate_value = metadata.get(key, None)
|
||||
if not aggregate_value or value not in aggregate_value:
|
||||
LOG.debug(_("%(host_state)s fails "
|
||||
"AggregateInstanceExtraSpecsFilter requirements, "
|
||||
"missing %(key)s,'%(value)s'="
|
||||
"'%(aggregate_value)s'"), locals())
|
||||
return False
|
||||
return True
|
@ -37,3 +37,19 @@ class TypeAffinityFilter(filters.BaseHostFilter):
|
||||
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
|
||||
|
||||
|
||||
class AggregateTypeAffinityFilter(filters.BaseHostFilter):
|
||||
"""AggregateTypeAffinityFilter limits instance_type by aggregate
|
||||
|
||||
return True if no instance_type key is set or if the aggregate metadata
|
||||
key 'instance_type' has the instance_type name as a value
|
||||
"""
|
||||
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
instance_type = filter_properties.get('instance_type')
|
||||
context = filter_properties['context'].elevated()
|
||||
metadata = db.aggregate_metadata_get_by_host(
|
||||
context, host_state.host, key='instance_type')
|
||||
return (len(metadata) == 0 or
|
||||
instance_type['name'] in metadata['instance_type'])
|
||||
|
@ -19,6 +19,7 @@ import httplib
|
||||
import stubout
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova.openstack.common import jsonutils
|
||||
@ -292,6 +293,28 @@ class HostFiltersTestCase(test.TestCase):
|
||||
params={'host': 'fake_host', 'instance_type_id': 2})
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_aggregate_type_filter(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['AggregateTypeAffinityFilter']()
|
||||
|
||||
filter_properties = {'context': self.context,
|
||||
'instance_type': {'name': 'fake1'}}
|
||||
filter2_properties = {'context': self.context,
|
||||
'instance_type': {'name': 'fake2'}}
|
||||
capabilities = {'enabled': True}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('fake_host', 'compute',
|
||||
{'capabilities': capabilities,
|
||||
'service': service})
|
||||
#True since no aggregates
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
#True since type matches aggregate, metadata
|
||||
self._create_aggregate_with_host(name='fake_aggregate',
|
||||
hosts=['fake_host'], metadata={'instance_type': 'fake1'})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
#False since type matches aggregate, metadata
|
||||
self.assertFalse(filt_cls.host_passes(host, filter2_properties))
|
||||
|
||||
def test_ram_filter_fails_on_memory(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['RamFilter']()
|
||||
@ -398,6 +421,63 @@ class HostFiltersTestCase(test.TestCase):
|
||||
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_aggregate_filter_passes_no_extra_specs(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['AggregateInstanceExtraSpecsFilter']()
|
||||
capabilities = {'enabled': True, 'opt1': 1, 'opt2': 2}
|
||||
|
||||
filter_properties = {'context': self.context, 'instance_type':
|
||||
{'memory_mb': 1024}}
|
||||
host = fakes.FakeHostState('host1', 'compute',
|
||||
{'capabilities': capabilities})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def _create_aggregate_with_host(self, name='fake_aggregate',
|
||||
metadata=None,
|
||||
hosts=['host1']):
|
||||
values = {'name': name,
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
result = db.aggregate_create(self.context.elevated(), values, metadata)
|
||||
for host in hosts:
|
||||
db.aggregate_host_add(self.context.elevated(), result.id, host)
|
||||
return result
|
||||
|
||||
def test_aggregate_filter_passes_extra_specs(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['AggregateInstanceExtraSpecsFilter']()
|
||||
extra_specs = {'opt1': '1', 'opt2': '2'}
|
||||
self._create_aggregate_with_host(metadata={'opt1': '1'})
|
||||
self._create_aggregate_with_host(name='fake2', metadata={'opt2': '2'})
|
||||
|
||||
filter_properties = {'context': self.context, 'instance_type':
|
||||
{'memory_mb': 1024, 'extra_specs': extra_specs}}
|
||||
host = fakes.FakeHostState('host1', 'compute', {'free_ram_mb': 1024})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_aggregate_filter_fails_extra_specs(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['AggregateInstanceExtraSpecsFilter']()
|
||||
extra_specs = {'opt1': 1, 'opt2': 3}
|
||||
self._create_aggregate_with_host(metadata={'opt1': '1'})
|
||||
self._create_aggregate_with_host(name='fake2', metadata={'opt2': '2'})
|
||||
filter_properties = {'context': self.context, 'instance_type':
|
||||
{'memory_mb': 1024, 'extra_specs': extra_specs}}
|
||||
host = fakes.FakeHostState('host1', 'compute', {'free_ram_mb': 1024})
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_aggregate_filter_fails_extra_specs_deleted_host(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['AggregateInstanceExtraSpecsFilter']()
|
||||
extra_specs = {'opt1': '1', 'opt2': '2'}
|
||||
self._create_aggregate_with_host(metadata={'opt1': '1'})
|
||||
agg2 = self._create_aggregate_with_host(name='fake2',
|
||||
metadata={'opt2': '2'})
|
||||
filter_properties = {'context': self.context, 'instance_type':
|
||||
{'memory_mb': 1024, 'extra_specs': extra_specs}}
|
||||
host = fakes.FakeHostState('host1', 'compute', {'free_ram_mb': 1024})
|
||||
db.aggregate_host_delete(self.context.elevated(), agg2.id, 'host1')
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_isolated_hosts_fails_isolated_on_non_isolated(self):
|
||||
self.flags(isolated_images=['isolated'], isolated_hosts=['isolated'])
|
||||
filt_cls = self.class_map['IsolatedHostsFilter']()
|
||||
|
@ -515,18 +515,68 @@ class AggregateDBApiTestCase(test.TestCase):
|
||||
self.assertEqual(_get_fake_aggr_metadata(), expected.metadetails)
|
||||
|
||||
def test_aggregate_get_by_host(self):
|
||||
"""Ensure we can get an aggregate by host."""
|
||||
"""Ensure we can get aggregates by host."""
|
||||
ctxt = context.get_admin_context()
|
||||
r1 = _create_aggregate_with_hosts(context=ctxt)
|
||||
r2 = db.aggregate_get_by_host(ctxt, 'foo.openstack.org')
|
||||
self.assertEqual(r1.id, r2.id)
|
||||
values = {'name': 'fake_aggregate2',
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
a1 = _create_aggregate_with_hosts(context=ctxt)
|
||||
a2 = _create_aggregate_with_hosts(context=ctxt, values=values)
|
||||
r1 = db.aggregate_get_by_host(ctxt, 'foo.openstack.org')
|
||||
self.assertEqual([a1.id, a2.id], [x.id for x in r1])
|
||||
|
||||
def test_aggregate_get_by_host_with_key(self):
|
||||
"""Ensure we can get aggregates by host."""
|
||||
ctxt = context.get_admin_context()
|
||||
values = {'name': 'fake_aggregate2',
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
a1 = _create_aggregate_with_hosts(context=ctxt,
|
||||
metadata={'goodkey': 'good'})
|
||||
a2 = _create_aggregate_with_hosts(context=ctxt, values=values)
|
||||
# filter result by key
|
||||
r1 = db.aggregate_get_by_host(ctxt, 'foo.openstack.org', key='goodkey')
|
||||
self.assertEqual([a1.id], [x.id for x in r1])
|
||||
|
||||
def test_aggregate_metdata_get_by_host(self):
|
||||
"""Ensure we can get aggregates by host."""
|
||||
ctxt = context.get_admin_context()
|
||||
values = {'name': 'fake_aggregate2',
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
values2 = {'name': 'fake_aggregate3',
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
a1 = _create_aggregate_with_hosts(context=ctxt)
|
||||
a2 = _create_aggregate_with_hosts(context=ctxt, values=values)
|
||||
a3 = _create_aggregate_with_hosts(context=ctxt, values=values2,
|
||||
hosts=['bar.openstack.org'], metadata={'badkey': 'bad'})
|
||||
r1 = db.aggregate_metadata_get_by_host(ctxt, 'foo.openstack.org')
|
||||
self.assertEqual(r1['fake_key1'], set(['fake_value1']))
|
||||
self.assertFalse('badkey' in r1)
|
||||
|
||||
def test_aggregate_metdata_get_by_host_with_key(self):
|
||||
"""Ensure we can get aggregates by host."""
|
||||
ctxt = context.get_admin_context()
|
||||
values = {'name': 'fake_aggregate2',
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
values2 = {'name': 'fake_aggregate3',
|
||||
'availability_zone': 'fake_avail_zone', }
|
||||
a1 = _create_aggregate_with_hosts(context=ctxt)
|
||||
a2 = _create_aggregate_with_hosts(context=ctxt, values=values)
|
||||
a3 = _create_aggregate_with_hosts(context=ctxt, values=values2,
|
||||
hosts=['foo.openstack.org'], metadata={'good': 'value'})
|
||||
r1 = db.aggregate_metadata_get_by_host(ctxt, 'foo.openstack.org',
|
||||
key='good')
|
||||
self.assertEqual(r1['good'], set(['value']))
|
||||
self.assertFalse('fake_key1' in r1)
|
||||
# Delete metadata
|
||||
db.aggregate_metadata_delete(ctxt, a3.id, 'good')
|
||||
r2 = db.aggregate_metadata_get_by_host(ctxt, 'foo.openstack.org',
|
||||
key='good')
|
||||
self.assertFalse('good' in r2)
|
||||
|
||||
def test_aggregate_get_by_host_not_found(self):
|
||||
"""Ensure AggregateHostNotFound is raised with unknown host."""
|
||||
ctxt = context.get_admin_context()
|
||||
_create_aggregate_with_hosts(context=ctxt)
|
||||
self.assertRaises(exception.AggregateHostNotFound,
|
||||
db.aggregate_get_by_host, ctxt, 'unknown_host')
|
||||
self.assertEqual([], db.aggregate_get_by_host(ctxt, 'unknown_host'))
|
||||
|
||||
def test_aggregate_delete_raise_not_found(self):
|
||||
"""Ensure AggregateNotFound is raised when deleting an aggregate."""
|
||||
|
@ -2149,9 +2149,9 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
|
||||
def __init__(self):
|
||||
self.metadetails = {"host": "test_host_uuid"}
|
||||
|
||||
def fake_aggregate_get_by_host(context, host):
|
||||
def fake_aggregate_get_by_host(context, host, key=None):
|
||||
self.assertEqual(FLAGS.host, host)
|
||||
return fake_aggregate()
|
||||
return [fake_aggregate()]
|
||||
|
||||
self.stubs.Set(db, "aggregate_get_by_host",
|
||||
fake_aggregate_get_by_host)
|
||||
@ -2163,9 +2163,9 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
|
||||
def __init__(self):
|
||||
self.metadetails = {"dest_other": "test_host_uuid"}
|
||||
|
||||
def fake_aggregate_get_by_host(context, host):
|
||||
def fake_aggregate_get_by_host(context, host, key=None):
|
||||
self.assertEqual(FLAGS.host, host)
|
||||
return fake_aggregate()
|
||||
return [fake_aggregate()]
|
||||
|
||||
self.stubs.Set(db, "aggregate_get_by_host",
|
||||
fake_aggregate_get_by_host)
|
||||
|
Loading…
Reference in New Issue
Block a user