From 9f4534ab584faeee1e24d4c1bb38a2b194f24626 Mon Sep 17 00:00:00 2001 From: Joe Gordon Date: Thu, 2 Aug 2012 17:44:18 -0700 Subject: [PATCH] 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 --- .../aggregate-update-post-resp.json | 8 +- .../aggregate-update-post-resp.xml | 8 +- .../aggregates-add-host-post-resp.json | 8 +- .../aggregates-add-host-post-resp.xml | 8 +- .../os-aggregates/aggregates-get-resp.json | 8 +- .../os-aggregates/aggregates-get-resp.xml | 8 +- .../aggregates-list-get-resp.json | 6 +- .../aggregates-list-get-resp.xml | 8 +- .../aggregates-metadata-post-resp.json | 3 +- .../aggregates-metadata-post-resp.xml | 3 +- .../aggregates-remove-host-post-resp.json | 6 +- .../aggregates-remove-host-post-resp.xml | 6 +- nova/api/ec2/cloud.py | 17 ++- nova/compute/api.py | 25 ++-- nova/db/api.py | 8 ++ nova/db/sqlalchemy/api.py | 52 +++++--- .../versions/146_aggregate_zones.py | 57 ++++++++ nova/db/sqlalchemy/models.py | 9 +- .../filters/availability_zone_filter.py | 17 ++- nova/tests/api/ec2/test_cloud.py | 10 +- .../compute/contrib/test_aggregates.py | 16 +-- nova/tests/compute/test_compute.py | 28 +--- nova/tests/conductor/test_conductor.py | 2 +- .../aggregate-update-post-resp.json.tpl | 4 +- .../aggregate-update-post-resp.xml.tpl | 4 +- .../aggregates-add-host-post-resp.json.tpl | 4 +- .../aggregates-add-host-post-resp.xml.tpl | 4 +- .../aggregates-get-resp.json.tpl | 4 +- .../os-aggregates/aggregates-get-resp.xml.tpl | 4 +- .../aggregates-list-get-resp.json.tpl | 4 +- .../aggregates-list-get-resp.xml.tpl | 4 +- .../aggregates-metadata-post-resp.json.tpl | 1 + .../aggregates-metadata-post-resp.xml.tpl | 1 + .../aggregates-remove-host-post-resp.json.tpl | 4 +- .../aggregates-remove-host-post-resp.xml.tpl | 4 +- nova/tests/scheduler/test_host_filters.py | 7 +- nova/tests/test_db_api.py | 123 ++++++++---------- nova/tests/test_migrations.py | 34 +++++ nova/tests/test_xenapi.py | 19 +-- 39 files changed, 355 insertions(+), 191 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/146_aggregate_zones.py diff --git a/doc/api_samples/os-aggregates/aggregate-update-post-resp.json b/doc/api_samples/os-aggregates/aggregate-update-post-resp.json index 81869e730a2d..6636f0a178f5 100644 --- a/doc/api_samples/os-aggregates/aggregate-update-post-resp.json +++ b/doc/api_samples/os-aggregates/aggregate-update-post-resp.json @@ -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" } } \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/aggregate-update-post-resp.xml b/doc/api_samples/os-aggregates/aggregate-update-post-resp.xml index ad9498aa0b0c..25227669be64 100644 --- a/doc/api_samples/os-aggregates/aggregate-update-post-resp.xml +++ b/doc/api_samples/os-aggregates/aggregate-update-post-resp.xml @@ -3,10 +3,12 @@ newname nova2 False - 2012-10-01 18:50:35.506667 - 2012-10-01 18:50:35.517397 + 2012-12-04 12:04:30.245284 + 2012-12-04 12:04:30.357795 None 1 - + + nova2 + \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.json b/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.json index 518f4176a9b1..1f7918ba8599 100644 --- a/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.json +++ b/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.json @@ -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 } diff --git a/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.xml b/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.xml index a4c9de5fd071..ad11f38597cf 100644 --- a/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.xml +++ b/doc/api_samples/os-aggregates/aggregates-add-host-post-resp.xml @@ -3,12 +3,14 @@ name nova False - 2012-10-01 18:50:35.236556 + 2012-12-04 12:04:27.574038 None - 7c9e00dbca5e4fb88538b021c0f933a5 + 392adba19dd449179804eaff16ff4a97 None 1 - + + nova + \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/aggregates-get-resp.json b/doc/api_samples/os-aggregates/aggregates-get-resp.json index cde446e51fb3..101a6584d2c1 100644 --- a/doc/api_samples/os-aggregates/aggregates-get-resp.json +++ b/doc/api_samples/os-aggregates/aggregates-get-resp.json @@ -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 } -} \ No newline at end of file +} diff --git a/doc/api_samples/os-aggregates/aggregates-get-resp.xml b/doc/api_samples/os-aggregates/aggregates-get-resp.xml index be1349bd28d3..431e59cf45a3 100644 --- a/doc/api_samples/os-aggregates/aggregates-get-resp.xml +++ b/doc/api_samples/os-aggregates/aggregates-get-resp.xml @@ -3,10 +3,12 @@ name nova False - 2012-10-01 18:50:34.764838 + 2012-11-16 06:22:25.587739 None None 1 - - \ No newline at end of file + + nova + + diff --git a/doc/api_samples/os-aggregates/aggregates-list-get-resp.json b/doc/api_samples/os-aggregates/aggregates-list-get-resp.json index 75b412b53870..53d278c63eab 100644 --- a/doc/api_samples/os-aggregates/aggregates-list-get-resp.json +++ b/doc/api_samples/os-aggregates/aggregates-list-get-resp.json @@ -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 } diff --git a/doc/api_samples/os-aggregates/aggregates-list-get-resp.xml b/doc/api_samples/os-aggregates/aggregates-list-get-resp.xml index c5590855b874..8d92e146647f 100644 --- a/doc/api_samples/os-aggregates/aggregates-list-get-resp.xml +++ b/doc/api_samples/os-aggregates/aggregates-list-get-resp.xml @@ -4,11 +4,13 @@ name nova False - 2012-10-01 18:50:34.970677 + 2012-11-16 06:22:25.935099 None None 1 - + + nova + - \ No newline at end of file + diff --git a/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.json b/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.json index dc4806a4f71d..33b4702ef0cf 100644 --- a/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.json +++ b/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.json @@ -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", diff --git a/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.xml b/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.xml index 7eeefb8b7af8..5e2193d06424 100644 --- a/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.xml +++ b/doc/api_samples/os-aggregates/aggregates-metadata-post-resp.xml @@ -3,12 +3,13 @@ name nova False - 2012-10-01 18:50:34.313003 + 2012-11-16 06:22:24.864471 None None 1 value + nova \ No newline at end of file diff --git a/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.json b/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.json index 497fcb7fb67e..ba9d4e00a751 100644 --- a/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.json +++ b/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.json @@ -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 } diff --git a/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml b/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml index dc8a55330b32..33dce2838477 100644 --- a/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml +++ b/doc/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml @@ -3,10 +3,12 @@ name nova False - 2012-10-01 18:50:35.236556 + 2012-12-04 12:04:29.722109 None None 1 - + + nova + \ No newline at end of file diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d40f25c4d947..208574903ff6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -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: diff --git a/nova/compute/api.py b/nova/compute/api.py index abbc0bd92f75..4ac04e7902c3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -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.""" diff --git a/nova/db/api.py b/nova/db/api.py index 1322c29e9c6c..8d93701c8c8c 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -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.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 57004be58bef..f89ebfaa36cb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -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) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/146_aggregate_zones.py b/nova/db/sqlalchemy/migrate_repo/versions/146_aggregate_zones.py new file mode 100644 index 000000000000..04f31ce5f565 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/146_aggregate_zones.py @@ -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) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index cdd140b6eee6..8a161efdff49 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -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.""" diff --git a/nova/scheduler/filters/availability_zone_filter.py b/nova/scheduler/filters/availability_zone_filter.py index 4e55d0b0c1ce..24ea0dd35671 100644 --- a/nova/scheduler/filters/availability_zone_filter.py +++ b/nova/scheduler/filters/availability_zone_filter.py @@ -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 diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index d452c18cb962..429746dac543 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -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']) diff --git a/nova/tests/api/openstack/compute/contrib/test_aggregates.py b/nova/tests/api/openstack/compute/contrib/test_aggregates.py index 0f60b8128f72..c57d6a91bd7e 100644 --- a/nova/tests/api/openstack/compute/contrib/test_aggregates.py +++ b/nova/tests/api/openstack/compute/contrib/test_aggregates.py @@ -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"}) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index dabb8bb891f9..823eeaf4e29d 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -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): diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py index dcbafec9ee26..fd87e420bacf 100644 --- a/nova/tests/conductor/test_conductor.py +++ b/nova/tests/conductor/test_conductor.py @@ -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') diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.json.tpl index 89a48ee570eb..119f78ad20c6 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.json.tpl @@ -6,7 +6,9 @@ "deleted_at": null, "hosts": [], "id": 1, - "metadata": {}, + "metadata": { + "availability_zone": "nova2" + }, "name": "newname", "updated_at": "%(timestamp)s" } diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.xml.tpl index 3f72a0b43018..071e1c43a698 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregate-update-post-resp.xml.tpl @@ -8,5 +8,7 @@ None 1 - + + nova2 + diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.json.tpl index ee0ea6c3d6d7..b311bb18e34a 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.json.tpl @@ -8,7 +8,9 @@ "%(compute_host)s" ], "id": 1, - "metadata": {}, + "metadata": { + "availability_zone": "nova" + }, "name": "name", "updated_at": null } diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.xml.tpl index 82a0401ad1b8..a45a01789431 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-add-host-post-resp.xml.tpl @@ -10,5 +10,7 @@ None 1 - + + nova + diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.json.tpl index 8ce7d9c407cf..6b94465c4299 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.json.tpl @@ -6,7 +6,9 @@ "deleted_at": null, "hosts": [], "id": 1, - "metadata": {}, + "metadata": { + "availability_zone": "nova" + }, "name": "name", "updated_at": null } diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.xml.tpl index 56f0dd3e888b..d59d10a84223 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-get-resp.xml.tpl @@ -8,5 +8,7 @@ None 1 - + + nova + diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.json.tpl index f373f02f79d4..bed47e730095 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.json.tpl @@ -7,7 +7,9 @@ "deleted_at": null, "hosts": [], "id": 1, - "metadata": {}, + "metadata": { + "availability_zone": "nova" + }, "name": "name", "updated_at": null } diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.xml.tpl index 417b1016f4ff..0a6173a0baff 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-list-get-resp.xml.tpl @@ -9,6 +9,8 @@ None 1 - + + nova + diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.json.tpl index 058a1ecf5364..f3493261776d 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.json.tpl @@ -7,6 +7,7 @@ "hosts": [], "id": 1, "metadata": { + "availability_zone": "nova", "key": "value" }, "name": "name", diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.xml.tpl index 9bbd1f0bdf07..5b229cfc9e27 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-metadata-post-resp.xml.tpl @@ -10,5 +10,6 @@ 1 value + nova diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.json.tpl index 8ce7d9c407cf..6b94465c4299 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.json.tpl @@ -6,7 +6,9 @@ "deleted_at": null, "hosts": [], "id": 1, - "metadata": {}, + "metadata": { + "availability_zone": "nova" + }, "name": "name", "updated_at": null } diff --git a/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml.tpl index 56f0dd3e888b..d59d10a84223 100644 --- a/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-aggregates/aggregates-remove-host-post-resp.xml.tpl @@ -8,5 +8,7 @@ None 1 - + + nova + diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index 07a1bc2b8589..b08da6baa8dd 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -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) diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index a17113a426e8..1a4509011ff4 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -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, diff --git a/nova/tests/test_migrations.py b/nova/tests/test_migrations.py index 125b2fe361dd..bcd858d967d3 100644 --- a/nova/tests/test_migrations.py +++ b/nova/tests/test_migrations.py @@ -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() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 8b57dfef4195..64659a21fe8d 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -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)