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