From d39193f5b5bfa3fb3438efdbdee5e8996c7e9645 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Wed, 18 Jan 2012 19:47:36 +0000 Subject: [PATCH] blueprint host-aggregates: OSAPI/virt integration, via nova.compute.api This commit introduces the first cut of integration between the OSAPI Admin extensions for host aggregates and the virt layer. This is part of a series of commits that have started with change: https://review.openstack.org/#change,3035 Change-Id: I75d8b616e3b8f8cef75d40d937e0dce9f29b16db --- nova/tests/test_compute.py | 211 +++++++++++++++++++++++++++++++++++++ nova/tests/test_db_api.py | 13 ++- 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index f416e4a5..44938e5b 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -31,6 +31,7 @@ import nova import nova.common.policy from nova import compute import nova.compute.api +from nova.compute import aggregate_states from nova.compute import instance_types from nova.compute import manager as compute_manager from nova.compute import power_state @@ -3011,6 +3012,216 @@ class ComputeAPITestCase(BaseTestCase): db.instance_destroy(self.context, instance['id']) +def fake_rpc_method(context, topic, msg, do_cast=True): + pass + + +def _create_service_entries(context, values={'avail_zone1': ['fake_host1', + 'fake_host2'], + 'avail_zone2': ['fake_host3'], }): + for avail_zone, hosts in values.iteritems(): + for host in hosts: + db.service_create(context, + {'host': host, + 'binary': 'nova-compute', + 'topic': 'compute', + 'report_count': 0, + 'availability_zone': avail_zone}) + return values + + +class ComputeAPIAggrTestCase(test.TestCase): + + def setUp(self): + super(ComputeAPIAggrTestCase, self).setUp() + self.api = compute.AggregateAPI() + self.context = context.get_admin_context() + self.stubs.Set(rpc, 'call', fake_rpc_method) + self.stubs.Set(rpc, 'cast', fake_rpc_method) + + def tearDown(self): + super(ComputeAPIAggrTestCase, self).tearDown() + + def test_update_aggregate_metadata(self): + aggr = self.api.create_aggregate(self.context, 'fake_aggregate', + 'fake_availability_zone') + metadata = {'foo_key1': 'foo_value1', + 'foo_key2': 'foo_value2', } + aggr = self.api.update_aggregate_metadata(self.context, aggr['id'], + metadata) + metadata['foo_key1'] = None + expected = self.api.update_aggregate_metadata(self.context, + aggr['id'], metadata) + self.assertDictMatch(expected['metadata'], {'foo_key2': 'foo_value2'}) + + def test_delete_aggregate(self): + """Ensure we can delete an aggregate.""" + aggr = self.api.create_aggregate(self.context, 'fake_aggregate', + 'fake_availability_zone') + self.api.delete_aggregate(self.context, aggr['id']) + expected = db.aggregate_get(self.context, aggr['id'], + read_deleted='yes') + self.assertNotEqual(aggr['operational_state'], + expected['operational_state']) + + def test_delete_non_empty_aggregate(self): + """Ensure InvalidAggregateAction is raised when non empty aggregate.""" + aggr = self.api.create_aggregate(self.context, 'fake_aggregate', + 'fake_availability_zone') + _create_service_entries(self.context, + {'fake_availability_zone': ['fake_host']}) + self.api.add_host_to_aggregate(self.context, aggr['id'], 'fake_host') + self.assertRaises(exception.InvalidAggregateAction, + self.api.delete_aggregate, self.context, aggr['id']) + + def test_add_host_to_aggregate(self): + """Ensure we can add a host to an aggregate.""" + values = _create_service_entries(self.context) + fake_zone = values.keys()[0] + fake_host = values[fake_zone][0] + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', fake_zone) + aggr = self.api.add_host_to_aggregate(self.context, + aggr['id'], fake_host) + self.assertEqual(aggr['operational_state'], aggregate_states.CHANGING) + + def test_add_host_to_aggregate_multiple(self): + """Ensure we can add multiple hosts to an aggregate.""" + values = _create_service_entries(self.context) + fake_zone = values.keys()[0] + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', fake_zone) + # let's mock the fact that the aggregate is active already! + status = {'operational_state': aggregate_states.ACTIVE} + db.aggregate_update(self.context, aggr['id'], status) + for host in values[fake_zone]: + aggr = self.api.add_host_to_aggregate(self.context, + aggr['id'], host) + self.assertEqual(len(aggr['hosts']), len(values[fake_zone])) + self.assertEqual(aggr['operational_state'], + aggregate_states.ACTIVE) + + def test_add_host_to_aggregate_invalid_changing_status(self): + """Ensure InvalidAggregateAction is raised when adding host while + aggregate is not ready.""" + values = _create_service_entries(self.context) + fake_zone = values.keys()[0] + fake_host = values[fake_zone][0] + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', fake_zone) + aggr = self.api.add_host_to_aggregate(self.context, + aggr['id'], fake_host) + self.assertEqual(aggr['operational_state'], + aggregate_states.CHANGING) + self.assertRaises(exception.InvalidAggregateAction, + self.api.add_host_to_aggregate, self.context, + aggr['id'], fake_host) + + def test_add_host_to_aggregate_invalid_dismissed_status(self): + """Ensure InvalidAggregateAction is raised when aggregate is + deleted.""" + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', 'fake_zone') + _create_service_entries(self.context, {'fake_zone': ['fake_host']}) + # let's mock the fact that the aggregate is dismissed! + status = {'operational_state': aggregate_states.DISMISSED} + db.aggregate_update(self.context, aggr['id'], status) + self.assertRaises(exception.InvalidAggregateAction, + self.api.add_host_to_aggregate, self.context, + aggr['id'], 'fake_host') + + def test_add_host_to_aggregate_invalid_error_status(self): + """Ensure InvalidAggregateAction is raised when aggregate is + in error.""" + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', 'fake_zone') + _create_service_entries(self.context, {'fake_zone': ['fake_host']}) + # let's mock the fact that the aggregate is in error! + status = {'operational_state': aggregate_states.ERROR} + db.aggregate_update(self.context, aggr['id'], status) + self.assertRaises(exception.InvalidAggregateAction, + self.api.add_host_to_aggregate, self.context, + aggr['id'], 'fake_host') + + 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_host']}) + 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_host') + + def test_add_host_to_aggregate_raise_not_found(self): + """Ensure ComputeHostNotFound is raised when adding invalid host.""" + aggr = self.api.create_aggregate(self.context, 'fake_aggregate', + 'fake_availability_zone') + self.assertRaises(exception.ComputeHostNotFound, + self.api.add_host_to_aggregate, + self.context, aggr['id'], 'invalid_host') + + def test_remove_host_from_aggregate_active(self): + """Ensure we can remove a host from an aggregate.""" + values = _create_service_entries(self.context) + fake_zone = values.keys()[0] + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', fake_zone) + # let's mock the fact that the aggregate is active already! + status = {'operational_state': aggregate_states.ACTIVE} + db.aggregate_update(self.context, aggr['id'], status) + for host in values[fake_zone]: + aggr = self.api.add_host_to_aggregate(self.context, + aggr['id'], host) + expected = self.api.remove_host_from_aggregate(self.context, + aggr['id'], + values[fake_zone][0]) + self.assertEqual(len(aggr['hosts']) - 1, len(expected['hosts'])) + self.assertEqual(expected['operational_state'], + aggregate_states.ACTIVE) + + def test_remove_host_from_aggregate_error(self): + """Ensure we can remove a host from an aggregate even if in error.""" + values = _create_service_entries(self.context) + fake_zone = values.keys()[0] + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', fake_zone) + # let's mock the fact that the aggregate is ready! + status = {'operational_state': aggregate_states.ACTIVE} + db.aggregate_update(self.context, aggr['id'], status) + for host in values[fake_zone]: + aggr = self.api.add_host_to_aggregate(self.context, + aggr['id'], host) + # let's mock the fact that the aggregate is in error! + status = {'operational_state': aggregate_states.ERROR} + expected = self.api.remove_host_from_aggregate(self.context, + aggr['id'], + values[fake_zone][0]) + self.assertEqual(len(aggr['hosts']) - 1, len(expected['hosts'])) + self.assertEqual(expected['operational_state'], + aggregate_states.ACTIVE) + + def test_remove_host_from_aggregate_invalid_dismissed_status(self): + """Ensure InvalidAggregateAction is raised when aggregate is + deleted.""" + aggr = self.api.create_aggregate(self.context, + 'fake_aggregate', 'fake_zone') + _create_service_entries(self.context, {'fake_zone': ['fake_host']}) + # let's mock the fact that the aggregate is dismissed! + status = {'operational_state': aggregate_states.DISMISSED} + db.aggregate_update(self.context, aggr['id'], status) + self.assertRaises(exception.InvalidAggregateAction, + self.api.remove_host_from_aggregate, self.context, + aggr['id'], 'fake_host') + + def test_remove_host_from_aggregate_raise_not_found(self): + """Ensure ComputeHostNotFound is raised when removing invalid host.""" + aggr = self.api.create_aggregate(self.context, 'fake_aggregate', + 'fake_availability_zone') + self.assertRaises(exception.ComputeHostNotFound, + self.api.remove_host_from_aggregate, + self.context, aggr['id'], 'invalid_host') + + class ComputePolicyTestCase(BaseTestCase): def setUp(self): diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index a384181d..121120db 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -304,7 +304,7 @@ class AggregateDBApiTestCase(test.TestCase): def test_aggregate_create(self): """Ensure aggregate can be created with no metadata.""" result = _create_aggregate(metadata=None) - self.assertEqual(result['operational_state'], 'building') + self.assertEqual(result['operational_state'], 'created') def test_aggregate_create_raise_exist_exc(self): """Ensure aggregate names are distinct.""" @@ -476,6 +476,17 @@ class AggregateDBApiTestCase(test.TestCase): 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.""" + ctxt = context.get_admin_context() + result = _create_aggregate_with_hosts(context=ctxt, metadata=None) + host = _get_fake_aggr_hosts()[0] + db.aggregate_host_delete(ctxt, result.id, host) + db.aggregate_host_add(ctxt, result.id, host) + expected = db.aggregate_host_get_all(ctxt, result.id, + read_deleted='no') + self.assertEqual(len(expected), 1) + def test_aggregate_host_add_duplicate_raise_conflict(self): """Ensure we cannot add host to distinct aggregates.""" ctxt = context.get_admin_context()