diff --git a/doc/notification_samples/server_group-add_member.json b/doc/notification_samples/server_group-add_member.json new file mode 100644 index 000000000000..7b44b9cb3e90 --- /dev/null +++ b/doc/notification_samples/server_group-add_member.json @@ -0,0 +1,11 @@ +{ + "priority": "INFO", + "payload": { + "$ref": "common_payloads/ServerGroupPayload.json#", + "nova_object.data": { + "members": ["54238a20-f9be-47a7-897e-d7cb0e4c03d0"] + } + }, + "event_type": "server_group.add_member", + "publisher_id": "nova-api:fake-mini" +} diff --git a/nova/compute/utils.py b/nova/compute/utils.py index d13b68819e33..6e25e1fdc910 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -665,6 +665,21 @@ def notify_about_server_group_action(context, group, action): notification.emit(context) +@rpc.if_notifications_enabled +def notify_about_server_group_add_member(context, group_id): + group = objects.InstanceGroup.get_by_uuid(context, group_id) + payload = sg_notification.ServerGroupPayload(group) + notification = sg_notification.ServerGroupNotification( + priority=fields.NotificationPriority.INFO, + publisher=notification_base.NotificationPublisher( + host=CONF.host, source=fields.NotificationSource.API), + event_type=notification_base.EventType( + object='server_group', + action=fields.NotificationAction.ADD_MEMBER), + payload=payload) + notification.emit(context) + + def refresh_info_cache_for_instance(context, instance): """Refresh the info cache for an instance. diff --git a/nova/notifications/objects/base.py b/nova/notifications/objects/base.py index 2d7b4b46499e..8ad69fcbd4f1 100644 --- a/nova/notifications/objects/base.py +++ b/nova/notifications/objects/base.py @@ -53,7 +53,8 @@ class EventType(NotificationObject): # Version 1.7: REMOVE_FIXED_IP replaced with INTERFACE_DETACH in # NotificationActionField enum # Version 1.8: IMPORT value is added to NotificationActionField enum - VERSION = '1.8' + # Version 1.9: ADD_MEMBER value is added to NotificationActionField enum + VERSION = '1.9' fields = { 'object': fields.StringField(nullable=False), diff --git a/nova/notifications/objects/server_group.py b/nova/notifications/objects/server_group.py index cafb56659159..81d2022a9069 100644 --- a/nova/notifications/objects/server_group.py +++ b/nova/notifications/objects/server_group.py @@ -49,6 +49,7 @@ class ServerGroupPayload(base.NotificationPayloadBase): self.populate_schema(group=cgroup) +@base.notification_sample('server_group-add_member.json') @base.notification_sample('server_group-create.json') @base.notification_sample('server_group-delete.json') @nova_base.NovaObjectRegistry.register_notification diff --git a/nova/objects/fields.py b/nova/objects/fields.py index 8921cd385f0d..2581340bfc0b 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -869,6 +869,7 @@ class NotificationAction(BaseNovaEnum): UNSHELVE = 'unshelve' ADD_HOST = 'add_host' REMOVE_HOST = 'remove_host' + ADD_MEMBER = 'add_member' ALL = (UPDATE, EXCEPTION, DELETE, PAUSE, UNPAUSE, RESIZE, VOLUME_SWAP, SUSPEND, POWER_ON, REBOOT, SHUTDOWN, SNAPSHOT, INTERFACE_ATTACH, @@ -879,7 +880,7 @@ class NotificationAction(BaseNovaEnum): LIVE_MIGRATION_ROLLBACK_DEST, REBUILD, INTERFACE_DETACH, RESIZE_CONFIRM, RESIZE_PREP, RESIZE_REVERT, SHELVE_OFFLOAD, SOFT_DELETE, TRIGGER_CRASH_DUMP, UNRESCUE, UNSHELVE, ADD_HOST, - REMOVE_HOST) + REMOVE_HOST, ADD_MEMBER) # TODO(rlrossit): These should be changed over to be a StateMachine enum from diff --git a/nova/objects/instance_group.py b/nova/objects/instance_group.py index 53d4bb1c6198..28d1545615bc 100644 --- a/nova/objects/instance_group.py +++ b/nova/objects/instance_group.py @@ -454,6 +454,7 @@ class InstanceGroup(base.NovaPersistentObject, base.NovaObject, instance_uuids) compute_utils.notify_about_server_group_update(context, "addmember", payload) + compute_utils.notify_about_server_group_add_member(context, group_uuid) return list(members) @base.remotable diff --git a/nova/tests/functional/db/test_quota.py b/nova/tests/functional/db/test_quota.py index 2f812fc919ec..0d4fc544e644 100644 --- a/nova/tests/functional/db/test_quota.py +++ b/nova/tests/functional/db/test_quota.py @@ -46,6 +46,8 @@ class QuotaTestCase(test.NoDBTestCase): # Create a server group the instances will use. group = objects.InstanceGroup(context=ctxt) + group.project_id = ctxt.project_id + group.user_id = ctxt.user_id group.create() instance_uuids = [] diff --git a/nova/tests/functional/notification_sample_tests/notification_sample_base.py b/nova/tests/functional/notification_sample_tests/notification_sample_base.py index 6aa88ab668ee..6c27ded349fb 100644 --- a/nova/tests/functional/notification_sample_tests/notification_sample_base.py +++ b/nova/tests/functional/notification_sample_tests/notification_sample_base.py @@ -149,7 +149,8 @@ class NotificationSampleTestBase(test.TestCase, self.assertJsonEqual(sample_obj, notification) - def _boot_a_server(self, expected_status='ACTIVE', extra_params=None): + def _boot_a_server(self, expected_status='ACTIVE', extra_params=None, + scheduler_hints=None): # We have to depend on a specific image and flavor to fix the content # of the notification that will be emitted @@ -201,6 +202,9 @@ class NotificationSampleTestBase(test.TestCase, server.update(extra_params) post = {'server': server} + if scheduler_hints: + post.update({"os:scheduler_hints": scheduler_hints}) + created_server = self.api.post_server(post) reservation_id = created_server['reservation_id'] created_server = self.api.get_servers( diff --git a/nova/tests/functional/notification_sample_tests/test_server_group.py b/nova/tests/functional/notification_sample_tests/test_server_group.py index d8675a8754f6..138626995176 100644 --- a/nova/tests/functional/notification_sample_tests/test_server_group.py +++ b/nova/tests/functional/notification_sample_tests/test_server_group.py @@ -9,6 +9,7 @@ # 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 nova.tests import fixtures from nova.tests.functional.notification_sample_tests \ import notification_sample_base from nova.tests.unit import fake_notifier @@ -17,6 +18,12 @@ from nova.tests.unit import fake_notifier class TestServerGroupNotificationSample( notification_sample_base.NotificationSampleTestBase): + def setUp(self): + self.flags(use_neutron=True) + super(TestServerGroupNotificationSample, self).setUp() + self.neutron = fixtures.NeutronFixture(self) + self.useFixture(self.neutron) + def test_server_group_create_delete(self): group_req = { "name": "test-server-group", @@ -37,3 +44,26 @@ class TestServerGroupNotificationSample( 'server_group-delete', replacements={'uuid': group['id']}, actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) + + def test_server_group_add_member(self): + group_req = { + "name": "test-server-group", + "policies": ["anti-affinity"]} + group = self.api.post_server_groups(group_req) + fake_notifier.reset() + + server = self._boot_a_server( + extra_params={'networks': [{'port': self.neutron.port_1['id']}]}, + scheduler_hints={"group": group['id']}) + self._wait_for_notification('instance.update') + # 0: server_group.add_member + # 1: instance.create.start + # 2: instance.create.end + # 3: instance.update + # (Due to adding server tags in the '_boot_a_server' method.) + self.assertEqual(4, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + self._verify_notification( + 'server_group-add_member', + replacements={'uuid': group['id'], + 'members': [server['id']]}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) diff --git a/nova/tests/unit/compute/test_compute_utils.py b/nova/tests/unit/compute/test_compute_utils.py index 2738e798f056..791fdfad9f18 100644 --- a/nova/tests/unit/compute/test_compute_utils.py +++ b/nova/tests/unit/compute/test_compute_utils.py @@ -1082,18 +1082,17 @@ class ServerGroupTestCase(test.TestCase): self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) + self.group = objects.InstanceGroup(context=self.context, + id=1, + uuid=uuids.server_group, + user_id=self.user_id, + project_id=self.project_id, + name="test-server-group", + policies=["anti-affinity"]) def test_notify_about_server_group_action(self): - uuid = uuids.instance - group = objects.InstanceGroup(context=self.context, - id=1, - uuid=uuid, - user_id=self.user_id, - project_id=self.project_id, - name="test-server-group", - policies=["anti-affinity"]) compute_utils.notify_about_server_group_action(self.context, - group, 'create') + self.group, 'create') self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1) notification = fake_notifier.VERSIONED_NOTIFICATIONS[0] expected = {'priority': 'INFO', @@ -1105,7 +1104,7 @@ class ServerGroupTestCase(test.TestCase): 'policies': [u'anti-affinity'], 'project_id': u'fake', 'user_id': u'fake', - 'uuid': uuid, + 'uuid': uuids.server_group, 'hosts': None, 'members': None }, @@ -1116,6 +1115,36 @@ class ServerGroupTestCase(test.TestCase): } self.assertEqual(notification, expected) + @mock.patch.object(objects.InstanceGroup, 'get_by_uuid') + def test_notify_about_server_group_add_member(self, mock_get_by_uuid): + self.group.members = [uuids.instance] + mock_get_by_uuid.return_value = self.group + compute_utils.notify_about_server_group_add_member( + self.context, uuids.server_group) + mock_get_by_uuid.assert_called_once_with(self.context, + uuids.server_group) + self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1) + notification = fake_notifier.VERSIONED_NOTIFICATIONS[0] + expected = {'priority': 'INFO', + 'event_type': u'server_group.add_member', + 'publisher_id': u'nova-api:fake-mini', + 'payload': { + 'nova_object.data': { + 'name': u'test-server-group', + 'policies': [u'anti-affinity'], + 'project_id': u'fake', + 'user_id': u'fake', + 'uuid': uuids.server_group, + 'hosts': None, + 'members': [uuids.instance] + }, + 'nova_object.name': 'ServerGroupPayload', + 'nova_object.namespace': 'nova', + 'nova_object.version': '1.0' + } + } + self.assertEqual(notification, expected) + class ComputeUtilsQuotaTestCase(test.TestCase): def setUp(self): diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py index eafe0335c53d..44205bc02597 100644 --- a/nova/tests/unit/notifications/objects/test_notification.py +++ b/nova/tests/unit/notifications/objects/test_notification.py @@ -373,7 +373,7 @@ notification_object_data = { 'AuditPeriodPayload': '1.0-2b429dd307b8374636703b843fa3f9cb', 'BandwidthPayload': '1.0-ee2616a7690ab78406842a2b68e34130', 'BlockDevicePayload': '1.0-29751e1b6d41b1454e36768a1e764df8', - 'EventType': '1.8-0f8fb2dbc76f10c7c56d1680c65ad0cf', + 'EventType': '1.9-0ef3553b1d64b4cf92780776eda80883', 'ExceptionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'ExceptionPayload': '1.0-27db46ee34cd97e39f2643ed92ad0cc5', 'FlavorNotification': '1.0-a73147b93b520ff0061865849d3dfa56', diff --git a/nova/tests/unit/objects/test_instance_group.py b/nova/tests/unit/objects/test_instance_group.py index 7bdea163895d..48aa992a4e1a 100644 --- a/nova/tests/unit/objects/test_instance_group.py +++ b/nova/tests/unit/objects/test_instance_group.py @@ -207,9 +207,11 @@ class _TestInstanceGroupObject(object): mock_notify.assert_called_once_with(self.context, "delete", {'server_group_id': _DB_UUID}) + @mock.patch('nova.compute.utils.notify_about_server_group_add_member') @mock.patch('nova.compute.utils.notify_about_server_group_update') @mock.patch('nova.objects.InstanceGroup._add_members_in_db') - def test_add_members(self, mock_members_add_db, mock_notify): + def test_add_members(self, mock_members_add_db, mock_notify, + mock_notify_add_member): fake_member_models = [{'instance_uuid': mock.sentinel.uuid}] fake_member_uuids = [mock.sentinel.uuid] mock_members_add_db.return_value = fake_member_models @@ -225,6 +227,7 @@ class _TestInstanceGroupObject(object): self.context, "addmember", {'instance_uuids': fake_member_uuids, 'server_group_id': _DB_UUID}) + mock_notify_add_member.assert_called_once_with(self.context, _DB_UUID) @mock.patch('nova.objects.InstanceList.get_by_filters') @mock.patch('nova.objects.InstanceGroup._get_from_db_by_uuid',