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")
|
message = _("%(err)s")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAggregateAction(Invalid):
|
||||||
|
message = _("Cannot perform action '%(action)s' on aggregate "
|
||||||
|
"%(aggregate_id)s. Reason: %(reason)s.")
|
||||||
|
|
||||||
|
|
||||||
class InstanceInvalidState(Invalid):
|
class InstanceInvalidState(Invalid):
|
||||||
message = _("Instance %(instance_uuid)s in %(attr)s %(state)s. Cannot "
|
message = _("Instance %(instance_uuid)s in %(attr)s %(state)s. Cannot "
|
||||||
"%(method)s while the instance is in this state.")
|
"%(method)s while the instance is in this state.")
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import nova
|
|||||||
import nova.common.policy
|
import nova.common.policy
|
||||||
from nova import compute
|
from nova import compute
|
||||||
import nova.compute.api
|
import nova.compute.api
|
||||||
|
from nova.compute import aggregate_states
|
||||||
from nova.compute import instance_types
|
from nova.compute import instance_types
|
||||||
from nova.compute import manager as compute_manager
|
from nova.compute import manager as compute_manager
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
@@ -3011,6 +3012,216 @@ class ComputeAPITestCase(BaseTestCase):
|
|||||||
db.instance_destroy(self.context, instance['id'])
|
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):
|
class ComputePolicyTestCase(BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ class AggregateDBApiTestCase(test.TestCase):
|
|||||||
def test_aggregate_create(self):
|
def test_aggregate_create(self):
|
||||||
"""Ensure aggregate can be created with no metadata."""
|
"""Ensure aggregate can be created with no metadata."""
|
||||||
result = _create_aggregate(metadata=None)
|
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):
|
def test_aggregate_create_raise_exist_exc(self):
|
||||||
"""Ensure aggregate names are distinct."""
|
"""Ensure aggregate names are distinct."""
|
||||||
@@ -476,6 +476,17 @@ class AggregateDBApiTestCase(test.TestCase):
|
|||||||
expected = db.aggregate_host_get_all(ctxt, result.id)
|
expected = db.aggregate_host_get_all(ctxt, result.id)
|
||||||
self.assertEqual(_get_fake_aggr_hosts(), expected)
|
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):
|
def test_aggregate_host_add_duplicate_raise_conflict(self):
|
||||||
"""Ensure we cannot add host to distinct aggregates."""
|
"""Ensure we cannot add host to distinct aggregates."""
|
||||||
ctxt = context.get_admin_context()
|
ctxt = context.get_admin_context()
|
||||||
|
|||||||
Reference in New Issue
Block a user