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 72ebbda706
commit 645d46b03f
28 changed files with 186 additions and 69 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

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

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

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