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:
Joe Gordon 2012-07-24 17:03:56 -07:00
parent 55f627fe07
commit 7d48c9fb40
5 changed files with 207 additions and 10 deletions

View 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

View File

@ -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'])

View File

@ -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']()

View File

@ -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."""

View File

@ -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)