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
This commit is contained in:
		@@ -261,6 +261,11 @@ class InvalidParameterValue(Invalid):
 | 
			
		||||
    message = _("%(err)s")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidAggregateAction(Invalid):
 | 
			
		||||
    message = _("Cannot perform action '%(action)s' on aggregate "
 | 
			
		||||
                "%(aggregate_id)s. Reason: %(reason)s.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InstanceInvalidState(Invalid):
 | 
			
		||||
    message = _("Instance %(instance_uuid)s in %(attr)s %(state)s. Cannot "
 | 
			
		||||
                "%(method)s while the instance is in this state.")
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user