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)