Enable Aggregate based availability zones

Instead of implementing availability zones in the service table,
availability zones are implemented using general aggregate metadata.

This patch does not remove availability zones from the service table, a
latter patch will do that.

* In theory supports a single compute node in multiple availability zones
* Drop availability_zone column from Aggregate table (is now a property)
* map aggregate metadata 'availability_zone' so API does not change

Implements blueprint aggregate-based-availability-zones

Change-Id: I2a2ac5bfaa526d639dff5efa392c051347dbd9bb
This commit is contained in:
Joe Gordon 2012-08-02 17:44:18 -07:00
parent 61e9e0112b
commit 9f4534ab58
39 changed files with 355 additions and 191 deletions

View File

@ -1,13 +1,15 @@
{
"aggregate": {
"availability_zone": "nova2",
"created_at": "2012-10-01T18:50:27.781065",
"created_at": "2012-12-04T12:04:27.075065",
"deleted": false,
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova2"
},
"name": "newname",
"updated_at": "2012-10-01T18:50:27.791392"
"updated_at": "2012-12-04T12:04:27.242597"
}
}

View File

@ -3,10 +3,12 @@
<name>newname</name>
<availability_zone>nova2</availability_zone>
<deleted>False</deleted>
<created_at>2012-10-01 18:50:35.506667</created_at>
<updated_at>2012-10-01 18:50:35.517397</updated_at>
<created_at>2012-12-04 12:04:30.245284</created_at>
<updated_at>2012-12-04 12:04:30.357795</updated_at>
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova2</availability_zone>
</metadata>
</aggregate>

View File

@ -1,14 +1,16 @@
{
"aggregate": {
"availability_zone": "nova",
"created_at": "2012-10-01T18:50:27.511586",
"created_at": "2012-12-04T12:04:24.399784",
"deleted": false,
"deleted_at": null,
"hosts": [
"581d29b9e3504d8a895caddb13839b15"
"0438c6a4e8d841ad823b801d681f4680"
],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -3,12 +3,14 @@
<name>name</name>
<availability_zone>nova</availability_zone>
<deleted>False</deleted>
<created_at>2012-10-01 18:50:35.236556</created_at>
<created_at>2012-12-04 12:04:27.574038</created_at>
<updated_at>None</updated_at>
<hosts>
<host>7c9e00dbca5e4fb88538b021c0f933a5</host>
<host>392adba19dd449179804eaff16ff4a97</host>
</hosts>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -1,13 +1,15 @@
{
"aggregate": {
"availability_zone": "nova",
"created_at": "2012-10-01T18:50:27.048605",
"created_at": "2012-11-16T06:22:23.032493",
"deleted": false,
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}
}
}

View File

@ -3,10 +3,12 @@
<name>name</name>
<availability_zone>nova</availability_zone>
<deleted>False</deleted>
<created_at>2012-10-01 18:50:34.764838</created_at>
<created_at>2012-11-16 06:22:25.587739</created_at>
<updated_at>None</updated_at>
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
</aggregate>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -2,12 +2,14 @@
"aggregates": [
{
"availability_zone": "nova",
"created_at": "2012-10-01T18:50:27.252869",
"created_at": "2012-11-16T06:22:23.361359",
"deleted": false,
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -4,11 +4,13 @@
<name>name</name>
<availability_zone>nova</availability_zone>
<deleted>False</deleted>
<created_at>2012-10-01 18:50:34.970677</created_at>
<created_at>2012-11-16 06:22:25.935099</created_at>
<updated_at>None</updated_at>
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>
</aggregates>
</aggregates>

View File

@ -1,12 +1,13 @@
{
"aggregate": {
"availability_zone": "nova",
"created_at": "2012-10-01T18:50:26.604176",
"created_at": "2012-11-16T06:22:22.342791",
"deleted": false,
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {
"availability_zone": "nova",
"key": "value"
},
"name": "name",

View File

@ -3,12 +3,13 @@
<name>name</name>
<availability_zone>nova</availability_zone>
<deleted>False</deleted>
<created_at>2012-10-01 18:50:34.313003</created_at>
<created_at>2012-11-16 06:22:24.864471</created_at>
<updated_at>None</updated_at>
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata>
<key>value</key>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -1,12 +1,14 @@
{
"aggregate": {
"availability_zone": "nova",
"created_at": "2012-10-01T18:50:27.511586",
"created_at": "2012-12-04T12:04:26.557909",
"deleted": false,
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -3,10 +3,12 @@
<name>name</name>
<availability_zone>nova</availability_zone>
<deleted>False</deleted>
<created_at>2012-10-01 18:50:35.236556</created_at>
<created_at>2012-12-04 12:04:29.722109</created_at>
<updated_at>None</updated_at>
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -257,12 +257,18 @@ class CloudController(object):
if not zone in available_zones:
available_zones.append(zone)
# aggregate based availability_zones
metadata = db.aggregate_host_get_by_metadata_key(context,
key='availability_zone')
for zone_set in metadata.values():
for zone in zone_set:
if zone not in available_zones:
available_zones.append(zone)
not_available_zones = []
for zone in [service.availability_zone for service in disabled_services
if not service['availability_zone'] in available_zones]:
if not zone in not_available_zones:
not_available_zones.append(zone)
return (available_zones, not_available_zones)
def _describe_availability_zones(self, context, **kwargs):
@ -294,6 +300,15 @@ class CloudController(object):
host_services.setdefault(service['host'], [])
host_services[service['host']].append(service)
# aggregate based available_zones
metadata = db.aggregate_host_get_by_metadata_key(context,
key='availability_zone')
# metdata: {machine: set( az1, az2 )}
for host, zones in metadata.items():
for zone in zones:
zone_hosts.setdefault(zone, [])
if host not in zone_hosts[zone]:
zone_hosts[zone].append(host)
result = []
for zone in available_zones:

View File

@ -2186,22 +2186,15 @@ class AggregateAPI(base.Base):
def create_aggregate(self, context, aggregate_name, availability_zone):
"""Creates the model for the aggregate."""
zones = [s['availability_zone'] for s in
self.db.service_get_all_by_topic(context,
CONF.compute_topic)]
if availability_zone in zones:
values = {"name": aggregate_name,
"availability_zone": availability_zone}
aggregate = self.db.aggregate_create(context, values)
aggregate = self._get_aggregate_info(context, aggregate)
# To maintain the same API result as before.
del aggregate['hosts']
del aggregate['metadata']
return aggregate
else:
raise exception.InvalidAggregateAction(action='create_aggregate',
aggregate_id="'N/A'",
reason='invalid zone')
values = {"name": aggregate_name}
aggregate = self.db.aggregate_create(context, values,
metadata={'availability_zone': availability_zone})
aggregate = self._get_aggregate_info(context, aggregate)
# To maintain the same API result as before.
del aggregate['hosts']
del aggregate['metadata']
return aggregate
def get_aggregate(self, context, aggregate_id):
"""Get an aggregate by id."""

View File

@ -1543,6 +1543,14 @@ def aggregate_metadata_get_by_host(context, host, key=None):
return IMPL.aggregate_metadata_get_by_host(context, host, key)
def aggregate_host_get_by_metadata_key(context, key):
"""Get hosts with a specific metadata key metadata for all aggregates.
Returns a dictionary where each key is a hostname and each value is the
key value"""
return IMPL.aggregate_host_get_by_metadata_key(context, key)
def aggregate_update(context, aggregate_id, values):
"""Update the attributes of an aggregates. If values contains a metadata
key, it updates the aggregate metadata too."""

View File

@ -4256,12 +4256,13 @@ def _aggregate_get_query(context, model_class, id_field=None, id=None,
@require_admin_context
def aggregate_create(context, values, metadata=None):
session = get_session()
aggregate = _aggregate_get_query(context,
models.Aggregate,
models.Aggregate.name,
values['name'],
session=session,
read_deleted='no').first()
query = _aggregate_get_query(context,
models.Aggregate,
models.Aggregate.name,
values['name'],
session=session,
read_deleted='no')
aggregate = query.options(joinedload('_metadata')).first()
if not aggregate:
aggregate = models.Aggregate()
aggregate.update(values)
@ -4274,15 +4275,16 @@ def aggregate_create(context, values, metadata=None):
raise exception.AggregateNameExists(aggregate_name=values['name'])
if metadata:
aggregate_metadata_add(context, aggregate.id, metadata)
return aggregate
return aggregate_get(context, aggregate.id)
@require_admin_context
def aggregate_get(context, aggregate_id):
aggregate = _aggregate_get_query(context,
models.Aggregate,
models.Aggregate.id,
aggregate_id).first()
query = _aggregate_get_query(context,
models.Aggregate,
models.Aggregate.id,
aggregate_id)
aggregate = query.options(joinedload('_metadata')).first()
if not aggregate:
raise exception.AggregateNotFound(aggregate_id=aggregate_id)
@ -4314,18 +4316,38 @@ def aggregate_metadata_get_by_host(context, host, key=None):
for agg in rows:
for kv in agg._metadata:
metadata[kv['key']].add(kv['value'])
return metadata
return dict(metadata)
@require_admin_context
def aggregate_host_get_by_metadata_key(context, key):
query = model_query(context, models.Aggregate).join(
"_metadata").filter(models.AggregateMetadata.key == key)
rows = query.all()
metadata = collections.defaultdict(set)
for agg in rows:
for agghost in agg._hosts:
metadata[agghost.host].add(agg._metadata[0]['value'])
return dict(metadata)
@require_admin_context
def aggregate_update(context, aggregate_id, values):
session = get_session()
aggregate = _aggregate_get_query(context,
aggregate = (_aggregate_get_query(context,
models.Aggregate,
models.Aggregate.id,
aggregate_id,
session=session).first()
session=session).
options(joinedload('_metadata')).first())
if aggregate:
if "availability_zone" in values:
az = values.pop('availability_zone')
if 'metadata' not in values:
values['metadata'] = {'availability_zone': az}
else:
values['metadata']['availability_zone'] = az
metadata = values.get('metadata')
if metadata is not None:
aggregate_metadata_add(context,
@ -4336,7 +4358,7 @@ def aggregate_update(context, aggregate_id, values):
aggregate.update(values)
aggregate.save(session=session)
values['metadata'] = metadata
return aggregate
return aggregate_get(context, aggregate.id)
else:
raise exception.AggregateNotFound(aggregate_id=aggregate_id)

View File

@ -0,0 +1,57 @@
# Copyright 2012 OpenStack LLC.
#
# 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 sqlalchemy import String, Column, MetaData, Table, delete, select
from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
aggregates = Table('aggregates', meta, autoload=True)
aggregate_metadata = Table('aggregate_metadata', meta, autoload=True)
# migrate data
record_list = list(aggregates.select().execute())
for rec in record_list:
row = aggregate_metadata.insert()
row.execute({'created_at': rec['created_at'],
'updated_at': rec['updated_at'],
'deleted_at': rec['deleted_at'],
'deleted': rec['deleted'],
'key': 'availability_zone',
'value': rec['availability_zone'],
'aggregate_id': rec['id'],
})
aggregates.drop_column('availability_zone')
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
aggregates = Table('aggregates', meta, autoload=True)
aggregate_metadata = Table('aggregate_metadata', meta, autoload=True)
availability_zone = Column('availability_zone', String(255))
aggregates.create_column(availability_zone)
# migrate data
aggregates.update().values(availability_zone=select(
[aggregate_metadata.c.value]).where(aggregates.c.id ==
aggregate_metadata.c.aggregate_id).where(aggregate_metadata.c.key ==
'availability_zone')).execute()
delete(aggregate_metadata, aggregate_metadata.c.key == 'availability_zone')
aggregates.c.availability_zone.alter(nullable=False)

View File

@ -866,7 +866,6 @@ class Aggregate(BASE, NovaBase):
__tablename__ = 'aggregates'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255))
availability_zone = Column(String(255), nullable=False)
_hosts = relationship(AggregateHost,
lazy="joined",
secondary="aggregate_hosts",
@ -893,7 +892,7 @@ class Aggregate(BASE, NovaBase):
backref='aggregates')
def _extra_keys(self):
return ['hosts', 'metadetails']
return ['hosts', 'metadetails', 'availability_zone']
@property
def hosts(self):
@ -903,6 +902,12 @@ class Aggregate(BASE, NovaBase):
def metadetails(self):
return dict([(m.key, m.value) for m in self._metadata])
@property
def availability_zone(self):
if 'availability_zone' not in self.metadetails:
return None
return self.metadetails['availability_zone']
class AgentBuild(BASE, NovaBase):
"""Represents an agent build."""

View File

@ -14,11 +14,17 @@
# under the License.
from nova import db
from nova.scheduler import filters
class AvailabilityZoneFilter(filters.BaseHostFilter):
"""Filters Hosts by availability zone."""
"""Filters Hosts by availability zone.
Works with both service and aggregate metadata.
For aggregate metadata uses the key 'availability_zone'
Note: in theory a compute node can be part of multiple availability_zones
"""
def host_passes(self, host_state, filter_properties):
spec = filter_properties.get('request_spec', {})
@ -26,5 +32,12 @@ class AvailabilityZoneFilter(filters.BaseHostFilter):
availability_zone = props.get('availability_zone')
if availability_zone:
return availability_zone == host_state.service['availability_zone']
if availability_zone == host_state.service['availability_zone']:
return True
context = filter_properties['context'].elevated()
metadata = db.aggregate_metadata_get_by_host(
context, host_state.host, key='availability_zone')
if 'availability_zone' in metadata:
return availability_zone in metadata['availability_zone']
return False
return True

View File

@ -710,8 +710,16 @@ class CloudTestCase(test.TestCase):
'topic': 'compute',
'report_count': 0,
'availability_zone': "zone2"})
# Aggregate based zones
agg = db.aggregate_create(self.context,
{'name': 'agg1'}, {'availability_zone': 'aggzones'})
db.aggregate_host_add(self.context, agg.id, 'host2_zones')
result = self.cloud.describe_availability_zones(self.context)
self.assertEqual(len(result['availabilityZoneInfo']), 3)
self.assertEqual(len(result['availabilityZoneInfo']), 4)
admin_ctxt = context.get_admin_context(read_deleted="no")
result = self.cloud.describe_availability_zones(admin_ctxt,
zone_name='verbose')
self.assertEqual(len(result['availabilityZoneInfo']), 18)
db.service_destroy(self.context, service1['id'])
db.service_destroy(self.context, service2['id'])

View File

@ -123,7 +123,7 @@ class AggregateTestCase(test.TestCase):
def test_create_with_extra_invalid_arg(self):
self.assertRaises(exc.HTTPBadRequest, self.controller.create,
self.req, dict(name="test",
availablity_zone="nova1",
availability_zone="nova1",
foo='bar'))
def test_show(self):
@ -183,9 +183,7 @@ class AggregateTestCase(test.TestCase):
return AGGREGATE
self.stubs.Set(self.controller.api, "update_aggregate",
stub_update_aggregate)
result = self.controller.update(self.req, "1", body=body)
self.assertEqual(AGGREGATE, result["aggregate"])
def test_update_with_no_updates(self):
@ -261,18 +259,6 @@ class AggregateTestCase(test.TestCase):
self.req, "bogus_aggregate",
body={"add_host": {"host": "host1"}})
def test_add_host_with_host_in_wrong_availability_zone(self):
def stub_add_host_to_aggregate(context, aggregate, host):
raise exception.InvalidAggregateAction(action='create_aggregate',
aggregate_id="'N/A'",
reason='wrong zone')
self.stubs.Set(self.controller.api, "add_host_to_aggregate",
stub_add_host_to_aggregate)
self.assertRaises(exc.HTTPConflict, self.controller.action,
self.req, "bogus_aggregate",
body={"add_host": {"host": "host1"}})
def test_add_host_with_missing_host(self):
self.assertRaises(exc.HTTPBadRequest, self.controller.action,
self.req, "1", body={"asdf": "asdf"})

View File

@ -5399,15 +5399,8 @@ class ComputeAPIAggrTestCase(BaseTestCase):
self.stubs.Set(rpc, 'call', fake_rpc_method)
self.stubs.Set(rpc, 'cast', fake_rpc_method)
def test_create_invalid_availability_zone(self):
"""Ensure InvalidAggregateAction is raised with wrong avail_zone."""
self.assertRaises(exception.InvalidAggregateAction,
self.api.create_aggregate,
self.context, 'fake_aggr', 'fake_avail_zone')
def test_update_aggregate_metadata(self):
"""Ensure metadata can be updated"""
_create_service_entries(self.context, {'fake_zone': ['fake_host']})
aggr = self.api.create_aggregate(self.context, 'fake_aggregate',
'fake_zone')
metadata = {'foo_key1': 'foo_value1',
@ -5418,11 +5411,11 @@ class ComputeAPIAggrTestCase(BaseTestCase):
expected = self.api.update_aggregate_metadata(self.context,
aggr['id'], metadata)
self.assertThat(expected['metadata'],
matchers.DictMatches({'foo_key2': 'foo_value2'}))
matchers.DictMatches({'availability_zone': 'fake_zone',
'foo_key2': 'foo_value2'}))
def test_delete_aggregate(self):
"""Ensure we can delete an aggregate."""
_create_service_entries(self.context, {'fake_zone': ['fake_host']})
aggr = self.api.create_aggregate(self.context, 'fake_aggregate',
'fake_zone')
self.api.delete_aggregate(self.context, aggr['id'])
@ -5463,19 +5456,8 @@ class ComputeAPIAggrTestCase(BaseTestCase):
aggr['id'], host)
self.assertEqual(len(aggr['hosts']), len(values[fake_zone]))
def test_add_host_to_aggregate_zones_mismatch(self):
"""Ensure InvalidAggregateAction is raised when zones don't match."""
_create_service_entries(self.context, {'fake_zoneX': ['fake_host1'],
'fake_zoneY': ['fake_host2']})
aggr = self.api.create_aggregate(self.context,
'fake_aggregate', 'fake_zoneY')
self.assertRaises(exception.InvalidAggregateAction,
self.api.add_host_to_aggregate,
self.context, aggr['id'], 'fake_host1')
def test_add_host_to_aggregate_raise_not_found(self):
"""Ensure ComputeHostNotFound is raised when adding invalid host."""
_create_service_entries(self.context, {'fake_zone': ['fake_host']})
aggr = self.api.create_aggregate(self.context, 'fake_aggregate',
'fake_zone')
self.assertRaises(exception.ComputeHostNotFound,
@ -5526,9 +5508,9 @@ class ComputeAggrTestCase(BaseTestCase):
def setUp(self):
super(ComputeAggrTestCase, self).setUp()
self.context = context.get_admin_context()
values = {'name': 'test_aggr',
'availability_zone': 'test_zone'}
self.aggr = db.aggregate_create(self.context, values)
values = {'name': 'test_aggr'}
az = {'availability_zone': 'test_zone'}
self.aggr = db.aggregate_create(self.context, values, metadata=az)
def test_add_aggregate_host(self):
def fake_driver_add_to_aggregate(context, aggregate, host, **_ignore):

View File

@ -129,7 +129,7 @@ class _BaseTestCase(object):
def _setup_aggregate_with_host(self):
aggregate_ref = db.aggregate_create(self.context.elevated(),
{'name': 'foo', 'availability_zone': 'foo'})
{'name': 'foo'}, metadata={'availability_zone': 'foo'})
self.conductor.aggregate_host_add(self.context, aggregate_ref, 'bar')

View File

@ -6,7 +6,9 @@
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova2"
},
"name": "newname",
"updated_at": "%(timestamp)s"
}

View File

@ -8,5 +8,7 @@
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova2</availability_zone>
</metadata>
</aggregate>

View File

@ -8,7 +8,9 @@
"%(compute_host)s"
],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -10,5 +10,7 @@
</hosts>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -6,7 +6,9 @@
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -8,5 +8,7 @@
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -7,7 +7,9 @@
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -9,6 +9,8 @@
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>
</aggregates>

View File

@ -7,6 +7,7 @@
"hosts": [],
"id": 1,
"metadata": {
"availability_zone": "nova",
"key": "value"
},
"name": "name",

View File

@ -10,5 +10,6 @@
<id>1</id>
<metadata>
<key>value</key>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -6,7 +6,9 @@
"deleted_at": null,
"hosts": [],
"id": 1,
"metadata": {},
"metadata": {
"availability_zone": "nova"
},
"name": "name",
"updated_at": null
}

View File

@ -8,5 +8,7 @@
<hosts/>
<deleted_at>None</deleted_at>
<id>1</id>
<metadata/>
<metadata>
<availability_zone>nova</availability_zone>
</metadata>
</aggregate>

View File

@ -748,8 +748,11 @@ class HostFiltersTestCase(test.TestCase):
def _create_aggregate_with_host(self, name='fake_aggregate',
metadata=None,
hosts=['host1']):
values = {'name': name,
'availability_zone': 'fake_avail_zone', }
values = {'name': name}
if metadata:
metadata['availability_zone'] = 'fake_avail_zone'
else:
metadata = {'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)

View File

@ -765,13 +765,13 @@ class DbApiTestCase(test.TestCase):
def _get_fake_aggr_values():
return {'name': 'fake_aggregate',
'availability_zone': 'fake_avail_zone', }
return {'name': 'fake_aggregate'}
def _get_fake_aggr_metadata():
return {'fake_key1': 'fake_value1',
'fake_key2': 'fake_value2'}
'fake_key2': 'fake_value2',
'availability_zone': 'fake_avail_zone'}
def _get_fake_aggr_hosts():
@ -802,28 +802,26 @@ class AggregateDBApiTestCase(test.TestCase):
self.project_id = 'fake'
self.context = context.RequestContext(self.user_id, self.project_id)
def test_aggregate_create(self):
"""Ensure aggregate can be created with no metadata."""
def test_aggregate_create_no_metadata(self):
result = _create_aggregate(metadata=None)
self.assertEquals(result['name'], 'fake_aggregate')
def test_aggregate_create_avoid_name_conflict(self):
"""Test we can avoid conflict on deleted aggregates."""
r1 = _create_aggregate(metadata=None)
db.aggregate_delete(context.get_admin_context(), r1['id'])
values = {'name': r1['name'], 'availability_zone': 'new_zone'}
r2 = _create_aggregate(values=values)
values = {'name': r1['name']}
metadata = {'availability_zone': 'new_zone'}
r2 = _create_aggregate(values=values, metadata=metadata)
self.assertEqual(r2['name'], values['name'])
self.assertEqual(r2['availability_zone'], values['availability_zone'])
self.assertEqual(r2['availability_zone'],
metadata['availability_zone'])
def test_aggregate_create_raise_exist_exc(self):
"""Ensure aggregate names are distinct."""
_create_aggregate(metadata=None)
self.assertRaises(exception.AggregateNameExists,
_create_aggregate, metadata=None)
def test_aggregate_get_raise_not_found(self):
"""Ensure AggregateNotFound is raised when getting an aggregate."""
ctxt = context.get_admin_context()
# this does not exist!
aggregate_id = 1
@ -832,7 +830,6 @@ class AggregateDBApiTestCase(test.TestCase):
ctxt, aggregate_id)
def test_aggregate_metadata_get_raise_not_found(self):
"""Ensure AggregateNotFound is raised when getting metadata."""
ctxt = context.get_admin_context()
# this does not exist!
aggregate_id = 1
@ -841,7 +838,6 @@ class AggregateDBApiTestCase(test.TestCase):
ctxt, aggregate_id)
def test_aggregate_create_with_metadata(self):
"""Ensure aggregate can be created with metadata."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt)
expected_metadata = db.aggregate_metadata_get(ctxt, result['id'])
@ -849,25 +845,25 @@ class AggregateDBApiTestCase(test.TestCase):
matchers.DictMatches(_get_fake_aggr_metadata()))
def test_aggregate_create_delete_create_with_metadata(self):
"""Ensure aggregate metadata is deleted bug 1052479."""
#test for bug 1052479
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt)
expected_metadata = db.aggregate_metadata_get(ctxt, result['id'])
self.assertThat(expected_metadata,
matchers.DictMatches(_get_fake_aggr_metadata()))
db.aggregate_delete(ctxt, result['id'])
result = _create_aggregate(metadata=None)
result = _create_aggregate(metadata={'availability_zone':
'fake_avail_zone'})
expected_metadata = db.aggregate_metadata_get(ctxt, result['id'])
self.assertEqual(expected_metadata, {})
self.assertEqual(expected_metadata, {'availability_zone':
'fake_avail_zone'})
def test_aggregate_create_low_privi_context(self):
"""Ensure right context is applied when creating aggregate."""
self.assertRaises(exception.AdminRequired,
db.aggregate_create,
self.context, _get_fake_aggr_values())
def test_aggregate_get(self):
"""Ensure we can get aggregate with all its relations."""
ctxt = context.get_admin_context()
result = _create_aggregate_with_hosts(context=ctxt)
expected = db.aggregate_get(ctxt, result['id'])
@ -875,20 +871,16 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertEqual(_get_fake_aggr_metadata(), expected['metadetails'])
def test_aggregate_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', }
values = {'name': 'fake_aggregate2'}
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', }
values = {'name': 'fake_aggregate2'}
a1 = _create_aggregate_with_hosts(context=ctxt,
metadata={'goodkey': 'good'})
a2 = _create_aggregate_with_hosts(context=ctxt, values=values)
@ -896,13 +888,10 @@ class AggregateDBApiTestCase(test.TestCase):
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."""
def test_aggregate_metadata_get_by_host(self):
ctxt = context.get_admin_context()
values = {'name': 'fake_aggregate2',
'availability_zone': 'fake_avail_zone', }
values2 = {'name': 'fake_aggregate3',
'availability_zone': 'fake_avail_zone', }
values = {'name': 'fake_aggregate2'}
values2 = {'name': 'fake_aggregate3'}
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,
@ -911,13 +900,10 @@ class AggregateDBApiTestCase(test.TestCase):
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."""
def test_aggregate_metadata_get_by_host_with_key(self):
ctxt = context.get_admin_context()
values = {'name': 'fake_aggregate2',
'availability_zone': 'fake_avail_zone', }
values2 = {'name': 'fake_aggregate3',
'availability_zone': 'fake_avail_zone', }
values = {'name': 'fake_aggregate2'}
values2 = {'name': 'fake_aggregate3'}
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,
@ -932,14 +918,24 @@ class AggregateDBApiTestCase(test.TestCase):
key='good')
self.assertFalse('good' in r2)
def test_aggregate_host_get_by_metadata_key(self):
ctxt = context.get_admin_context()
values = {'name': 'fake_aggregate2'}
values2 = {'name': 'fake_aggregate3'}
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_host_get_by_metadata_key(ctxt, key='good')
self.assertEqual(r1, {'foo.openstack.org': set(['value'])})
self.assertFalse('fake_key1' in r1)
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.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."""
ctxt = context.get_admin_context()
# this does not exist!
aggregate_id = 1
@ -948,7 +944,6 @@ class AggregateDBApiTestCase(test.TestCase):
ctxt, aggregate_id)
def test_aggregate_delete(self):
"""Ensure we can delete an aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt, metadata=None)
db.aggregate_delete(ctxt, result['id'])
@ -959,9 +954,10 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertEqual(aggregate['deleted'], True)
def test_aggregate_update(self):
"""Ensure an aggregate can be updated."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt, metadata=None)
result = _create_aggregate(context=ctxt, metadata={'availability_zone':
'fake_avail_zone'})
self.assertEqual(result.availability_zone, 'fake_avail_zone')
new_values = _get_fake_aggr_values()
new_values['availability_zone'] = 'different_avail_zone'
updated = db.aggregate_update(ctxt, 1, new_values)
@ -969,18 +965,20 @@ class AggregateDBApiTestCase(test.TestCase):
updated['availability_zone'])
def test_aggregate_update_with_metadata(self):
"""Ensure an aggregate can be updated with metadata."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt, metadata=None)
values = _get_fake_aggr_values()
values['metadata'] = _get_fake_aggr_metadata()
values['availability_zone'] = 'different_avail_zone'
db.aggregate_update(ctxt, 1, values)
expected = db.aggregate_metadata_get(ctxt, result['id'])
self.assertThat(_get_fake_aggr_metadata(),
updated = db.aggregate_get(ctxt, result['id'])
self.assertThat(values['metadata'],
matchers.DictMatches(expected))
self.assertNotEqual(result.availability_zone,
updated.availability_zone)
def test_aggregate_update_with_existing_metadata(self):
"""Ensure an aggregate can be updated with existing metadata."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt)
values = _get_fake_aggr_values()
@ -991,7 +989,6 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertThat(values['metadata'], matchers.DictMatches(expected))
def test_aggregate_update_raise_not_found(self):
"""Ensure AggregateNotFound is raised when updating an aggregate."""
ctxt = context.get_admin_context()
# this does not exist!
aggregate_id = 1
@ -1000,26 +997,22 @@ class AggregateDBApiTestCase(test.TestCase):
db.aggregate_update, ctxt, aggregate_id, new_values)
def test_aggregate_get_all(self):
"""Ensure we can get all aggregates."""
ctxt = context.get_admin_context()
counter = 3
for c in xrange(counter):
_create_aggregate(context=ctxt,
values={'name': 'fake_aggregate_%d' % c,
'availability_zone': 'fake_avail_zone'},
values={'name': 'fake_aggregate_%d' % c},
metadata=None)
results = db.aggregate_get_all(ctxt)
self.assertEqual(len(results), counter)
def test_aggregate_get_all_non_deleted(self):
"""Ensure we get only non-deleted aggregates."""
ctxt = context.get_admin_context()
add_counter = 5
remove_counter = 2
aggregates = []
for c in xrange(1, add_counter):
values = {'name': 'fake_aggregate_%d' % c,
'availability_zone': 'fake_avail_zone'}
values = {'name': 'fake_aggregate_%d' % c}
aggregates.append(_create_aggregate(context=ctxt,
values=values, metadata=None))
for c in xrange(1, remove_counter):
@ -1028,7 +1021,6 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertEqual(len(results), add_counter - remove_counter)
def test_aggregate_metadata_add(self):
"""Ensure we can add metadata for the aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt, metadata=None)
metadata = _get_fake_aggr_metadata()
@ -1037,7 +1029,6 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertThat(metadata, matchers.DictMatches(expected))
def test_aggregate_metadata_update(self):
"""Ensure we can update metadata for the aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt)
metadata = _get_fake_aggr_metadata()
@ -1050,7 +1041,6 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertThat(metadata, matchers.DictMatches(expected))
def test_aggregate_metadata_delete(self):
"""Ensure we can delete metadata for the aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt, metadata=None)
metadata = _get_fake_aggr_metadata()
@ -1060,8 +1050,17 @@ class AggregateDBApiTestCase(test.TestCase):
del metadata[metadata.keys()[0]]
self.assertThat(metadata, matchers.DictMatches(expected))
def test_aggregate_remove_availability_zone(self):
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt, metadata={'availability_zone':
'fake_avail_zone'})
db.aggregate_metadata_delete(ctxt, result.id, 'availability_zone')
expected = db.aggregate_metadata_get(ctxt, result.id)
aggregate = db.aggregate_get(ctxt, result.id)
self.assertEquals(aggregate.availability_zone, None)
self.assertThat({}, matchers.DictMatches(expected))
def test_aggregate_metadata_delete_raise_not_found(self):
"""Ensure AggregateMetadataNotFound is raised when deleting."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt)
self.assertRaises(exception.AggregateMetadataNotFound,
@ -1069,14 +1068,12 @@ class AggregateDBApiTestCase(test.TestCase):
ctxt, result['id'], 'foo_key')
def test_aggregate_host_add(self):
"""Ensure we can add host to the aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate_with_hosts(context=ctxt, metadata=None)
expected = db.aggregate_host_get_all(ctxt, result['id'])
self.assertEqual(_get_fake_aggr_hosts(), expected)
def test_aggregate_host_add_deleted(self):
"""Ensure we can add a host that was previously deleted."""
def test_aggregate_host_re_add(self):
ctxt = context.get_admin_context()
result = _create_aggregate_with_hosts(context=ctxt, metadata=None)
host = _get_fake_aggr_hosts()[0]
@ -1086,19 +1083,16 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertEqual(len(expected), 1)
def test_aggregate_host_add_duplicate_works(self):
"""Ensure we can add host to distinct aggregates."""
ctxt = context.get_admin_context()
r1 = _create_aggregate_with_hosts(context=ctxt, metadata=None)
r2 = _create_aggregate_with_hosts(ctxt,
values={'name': 'fake_aggregate2',
'availability_zone': 'fake_avail_zone2', },
metadata=None)
values={'name': 'fake_aggregate2'},
metadata={'availability_zone': 'fake_avail_zone2'})
h1 = db.aggregate_host_get_all(ctxt, r1['id'])
h2 = db.aggregate_host_get_all(ctxt, r2['id'])
self.assertEqual(h1, h2)
def test_aggregate_host_add_duplicate_raise_exist_exc(self):
"""Ensure we cannot add host to the same aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate_with_hosts(context=ctxt, metadata=None)
self.assertRaises(exception.AggregateHostExists,
@ -1106,7 +1100,6 @@ class AggregateDBApiTestCase(test.TestCase):
ctxt, result['id'], _get_fake_aggr_hosts()[0])
def test_aggregate_host_add_raise_not_found(self):
"""Ensure AggregateFound when adding a host."""
ctxt = context.get_admin_context()
# this does not exist!
aggregate_id = 1
@ -1116,7 +1109,6 @@ class AggregateDBApiTestCase(test.TestCase):
ctxt, aggregate_id, host)
def test_aggregate_host_delete(self):
"""Ensure we can add host to the aggregate."""
ctxt = context.get_admin_context()
result = _create_aggregate_with_hosts(context=ctxt, metadata=None)
db.aggregate_host_delete(ctxt, result['id'],
@ -1125,7 +1117,6 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertEqual(0, len(expected))
def test_aggregate_host_delete_raise_not_found(self):
"""Ensure AggregateHostNotFound is raised when deleting a host."""
ctxt = context.get_admin_context()
result = _create_aggregate(context=ctxt)
self.assertRaises(exception.AggregateHostNotFound,

View File

@ -297,3 +297,37 @@ class TestMigrations(test.TestCase):
self.assertEqual(version,
migration_api.db_version(engine,
TestMigrations.REPOSITORY))
def test_migration_146(self):
name = 'name'
az = 'custom_az'
def _145_check():
agg = aggregates.select(aggregates.c.id == 1).execute().first()
self.assertEqual(name, agg.name)
self.assertEqual(az, agg.availability_zone)
for key, engine in self.engines.items():
migration_api.version_control(engine, TestMigrations.REPOSITORY,
migration.INIT_VERSION)
migration_api.upgrade(engine, TestMigrations.REPOSITORY, 145)
metadata = sqlalchemy.schema.MetaData()
metadata.bind = engine
aggregates = sqlalchemy.Table('aggregates', metadata,
autoload=True)
aggregates.insert().values(id=1, availability_zone=az,
aggregate_name=1, name=name).execute()
_145_check()
migration_api.upgrade(engine, TestMigrations.REPOSITORY, 146)
aggregate_metadata = sqlalchemy.Table('aggregate_metadata',
metadata, autoload=True)
metadata = aggregate_metadata.select(aggregate_metadata.c.
aggregate_id == 1).execute().first()
self.assertEqual(az, metadata['value'])
migration_api.downgrade(engine, TestMigrations.REPOSITORY, 145)
_145_check()

View File

@ -2222,11 +2222,12 @@ class XenAPIAggregateTestCase(stubs.XenAPITestBase):
self.compute = importutils.import_object(CONF.compute_manager)
self.api = compute_api.AggregateAPI()
values = {'name': 'test_aggr',
'availability_zone': 'test_zone',
'metadata': {pool_states.POOL_FLAG: 'XenAPI'}}
'metadata': {'availability_zone': 'test_zone',
pool_states.POOL_FLAG: 'XenAPI'}}
self.aggr = db.aggregate_create(self.context, values)
self.fake_metadata = {pool_states.POOL_FLAG: 'XenAPI',
'master_compute': 'host',
'availability_zone': 'fake_zone',
pool_states.KEY: pool_states.ACTIVE,
'host': xenapi_fake.get_record('host',
host_ref)['uuid']}
@ -2306,9 +2307,10 @@ class XenAPIAggregateTestCase(stubs.XenAPITestBase):
self.conn._session.call_xenapi("pool.create", {"name": "asdf"})
values = {"name": 'fake_aggregate',
"availability_zone": 'fake_zone'}
'metadata': {'availability_zone': 'fake_zone'}}
result = db.aggregate_create(self.context, values)
metadata = {pool_states.POOL_FLAG: "XenAPI",
metadata = {'availability_zone': 'fake_zone',
pool_states.POOL_FLAG: "XenAPI",
pool_states.KEY: pool_states.CREATED}
db.aggregate_metadata_add(self.context, result['id'], metadata)
@ -2358,7 +2360,8 @@ class XenAPIAggregateTestCase(stubs.XenAPITestBase):
self.conn._pool.remove_from_aggregate(self.context, aggregate, "host")
result = db.aggregate_get(self.context, aggregate['id'])
self.assertTrue(fake_clear_pool.called)
self.assertThat({pool_states.POOL_FLAG: 'XenAPI',
self.assertThat({'availability_zone': 'fake_zone',
pool_states.POOL_FLAG: 'XenAPI',
pool_states.KEY: pool_states.ACTIVE},
matchers.DictMatches(result['metadetails']))
@ -2375,9 +2378,9 @@ class XenAPIAggregateTestCase(stubs.XenAPITestBase):
aggr_zone='fake_zone',
aggr_state=pool_states.CREATED,
hosts=['host'], metadata=None):
values = {"name": aggr_name,
"availability_zone": aggr_zone}
result = db.aggregate_create(self.context, values)
values = {"name": aggr_name}
result = db.aggregate_create(self.context, values,
metadata={'availability_zone': aggr_zone})
pool_flag = {pool_states.POOL_FLAG: "XenAPI",
pool_states.KEY: aggr_state}
db.aggregate_metadata_add(self.context, result['id'], pool_flag)