Move cluster status notifications out of driver

Move cluster status change notifications into the
periodic task so that drivers do not have to have
any knowledge of Magnum notification strategy.

Change-Id: I5c71dd780f7bd6d4b683e491f5b4ce22cecb396c
Partial-Blueprint: bp-driver-consolodation
This commit is contained in:
Randall Burt 2016-12-05 13:24:30 -06:00
parent 759c1b3b2b
commit 84a9464957
4 changed files with 33 additions and 87 deletions

View File

@ -12,7 +12,6 @@
import abc import abc
import os import os
from pycadf import cadftaxonomy as taxonomy
import six import six
from oslo_config import cfg from oslo_config import cfg
@ -151,17 +150,6 @@ class HeatDriver(driver.Driver):
class HeatPoller(object): class HeatPoller(object):
status_to_event = {
fields.ClusterStatus.DELETE_COMPLETE: taxonomy.ACTION_DELETE,
fields.ClusterStatus.CREATE_COMPLETE: taxonomy.ACTION_CREATE,
fields.ClusterStatus.UPDATE_COMPLETE: taxonomy.ACTION_UPDATE,
fields.ClusterStatus.ROLLBACK_COMPLETE: taxonomy.ACTION_UPDATE,
fields.ClusterStatus.CREATE_FAILED: taxonomy.ACTION_CREATE,
fields.ClusterStatus.DELETE_FAILED: taxonomy.ACTION_DELETE,
fields.ClusterStatus.UPDATE_FAILED: taxonomy.ACTION_UPDATE,
fields.ClusterStatus.ROLLBACK_FAILED: taxonomy.ACTION_UPDATE
}
def __init__(self, openstack_client, cluster, cluster_driver): def __init__(self, openstack_client, cluster, cluster_driver):
self.openstack_client = openstack_client self.openstack_client = openstack_client
self.context = self.openstack_client.context self.context = self.openstack_client.context
@ -184,18 +172,10 @@ class HeatPoller(object):
# so another user/client can call delete cluster/stack. # so another user/client can call delete cluster/stack.
if stack.stack_status == fields.ClusterStatus.DELETE_COMPLETE: if stack.stack_status == fields.ClusterStatus.DELETE_COMPLETE:
self._delete_complete() self._delete_complete()
# TODO(randall): Move the status notification up the stack
conductor_utils.notify_about_cluster_operation(
self.context, self.status_to_event[stack.stack_status],
taxonomy.OUTCOME_SUCCESS)
if stack.stack_status in (fields.ClusterStatus.CREATE_COMPLETE, if stack.stack_status in (fields.ClusterStatus.CREATE_COMPLETE,
fields.ClusterStatus.UPDATE_COMPLETE): fields.ClusterStatus.UPDATE_COMPLETE):
self._sync_cluster_and_template_status(stack) self._sync_cluster_and_template_status(stack)
# TODO(randall): Move the status notification up the stack
conductor_utils.notify_about_cluster_operation(
self.context, self.status_to_event[stack.stack_status],
taxonomy.OUTCOME_SUCCESS)
elif stack.stack_status != self.cluster.status: elif stack.stack_status != self.cluster.status:
self._sync_cluster_status(stack) self._sync_cluster_status(stack)
@ -206,10 +186,6 @@ class HeatPoller(object):
fields.ClusterStatus.ROLLBACK_FAILED): fields.ClusterStatus.ROLLBACK_FAILED):
self._sync_cluster_and_template_status(stack) self._sync_cluster_and_template_status(stack)
self._cluster_failed(stack) self._cluster_failed(stack)
# TODO(randall): Move the status notification up the stack
conductor_utils.notify_about_cluster_operation(
self.context, self.status_to_event[stack.stack_status],
taxonomy.OUTCOME_FAILURE)
def _delete_complete(self): def _delete_complete(self):
LOG.info(_LI('Cluster has been deleted, stack_id: %s') LOG.info(_LI('Cluster has been deleted, stack_id: %s')
@ -220,7 +196,6 @@ class HeatPoller(object):
self.cluster) self.cluster)
cert_manager.delete_certificates_from_cluster(self.cluster, cert_manager.delete_certificates_from_cluster(self.cluster,
context=self.context) context=self.context)
self.cluster.destroy()
except exception.ClusterNotFound: except exception.ClusterNotFound:
LOG.info(_LI('The cluster %s has been deleted by others.') LOG.info(_LI('The cluster %s has been deleted by others.')
% self.cluster.uuid) % self.cluster.uuid)
@ -274,10 +249,6 @@ class HeatPoller(object):
self.cluster.status_reason = _("Stack with id %s not found in " self.cluster.status_reason = _("Stack with id %s not found in "
"Heat.") % self.cluster.stack_id "Heat.") % self.cluster.stack_id
self.cluster.save() self.cluster.save()
# TODO(randall): Move the status notification up the stack
conductor_utils.notify_about_cluster_operation(
self.context, self.status_to_event[self.cluster.status],
taxonomy.OUTCOME_FAILURE)
LOG.info(_LI("Cluster with id %(id)s has been set to " LOG.info(_LI("Cluster with id %(id)s has been set to "
"%(status)s due to stack with id %(sid)s " "%(status)s due to stack with id %(sid)s "
"not found in Heat."), "not found in Heat."),

View File

@ -19,9 +19,12 @@ from oslo_log import log
from oslo_service import loopingcall from oslo_service import loopingcall
from oslo_service import periodic_task from oslo_service import periodic_task
from pycadf import cadftaxonomy as taxonomy
from magnum.common import context from magnum.common import context
from magnum.common import rpc from magnum.common import rpc
from magnum.conductor import monitors from magnum.conductor import monitors
from magnum.conductor import utils as conductor_utils
import magnum.conf import magnum.conf
from magnum.drivers.common import driver from magnum.drivers.common import driver
from magnum.i18n import _LW from magnum.i18n import _LW
@ -44,6 +47,17 @@ def set_context(func):
class ClusterUpdateJob(object): class ClusterUpdateJob(object):
status_to_event = {
objects.fields.ClusterStatus.DELETE_COMPLETE: taxonomy.ACTION_DELETE,
objects.fields.ClusterStatus.CREATE_COMPLETE: taxonomy.ACTION_CREATE,
objects.fields.ClusterStatus.UPDATE_COMPLETE: taxonomy.ACTION_UPDATE,
objects.fields.ClusterStatus.ROLLBACK_COMPLETE: taxonomy.ACTION_UPDATE,
objects.fields.ClusterStatus.CREATE_FAILED: taxonomy.ACTION_CREATE,
objects.fields.ClusterStatus.DELETE_FAILED: taxonomy.ACTION_DELETE,
objects.fields.ClusterStatus.UPDATE_FAILED: taxonomy.ACTION_UPDATE,
objects.fields.ClusterStatus.ROLLBACK_FAILED: taxonomy.ACTION_UPDATE
}
def __init__(self, ctx, cluster): def __init__(self, ctx, cluster):
self.ctx = ctx self.ctx = ctx
self.cluster = cluster self.cluster = cluster
@ -54,13 +68,22 @@ class ClusterUpdateJob(object):
cdriver = driver.Driver.get_driver_for_cluster(self.ctx, self.cluster) cdriver = driver.Driver.get_driver_for_cluster(self.ctx, self.cluster)
# ask the driver to sync status # ask the driver to sync status
cdriver.update_cluster_status(self.ctx, self.cluster) cdriver.update_cluster_status(self.ctx, self.cluster)
# end the "loop"
LOG.debug("Status for cluster %s updated to %s (%s)", LOG.debug("Status for cluster %s updated to %s (%s)",
self.cluster.id, self.cluster.status, self.cluster.id, self.cluster.status,
self.cluster.status_reason) self.cluster.status_reason)
# status update notifications
if self.cluster.status.endswith("_COMPLETE"):
conductor_utils.notify_about_cluster_operation(
self.ctx, self.status_to_event[self.cluster.status],
taxonomy.OUTCOME_SUCCESS)
if self.cluster.status.endswith("_FAILED"):
conductor_utils.notify_about_cluster_operation(
self.ctx, self.status_to_event[self.cluster.status],
taxonomy.OUTCOME_FAILURE)
# if we're done with it, delete it # if we're done with it, delete it
if self.cluster.status == objects.fields.ClusterStatus.DELETE_COMPLETE: if self.cluster.status == objects.fields.ClusterStatus.DELETE_COMPLETE:
self.cluster.destroy() self.cluster.destroy()
# end the "loop"
raise loopingcall.LoopingCallDone() raise loopingcall.LoopingCallDone()

View File

@ -12,7 +12,6 @@
import mock import mock
from mock import patch from mock import patch
from pycadf import cadftaxonomy as taxonomy
import magnum.conf import magnum.conf
from magnum.drivers.heat import driver as heat_driver from magnum.drivers.heat import driver as heat_driver
@ -20,7 +19,6 @@ from magnum.drivers.k8s_fedora_atomic_v1 import driver as k8s_atomic_dr
from magnum import objects from magnum import objects
from magnum.objects.fields import ClusterStatus as cluster_status from magnum.objects.fields import ClusterStatus as cluster_status
from magnum.tests import base from magnum.tests import base
from magnum.tests import fake_notifier
from magnum.tests.unit.db import utils from magnum.tests.unit.db import utils
CONF = magnum.conf.CONF CONF = magnum.conf.CONF
@ -52,54 +50,6 @@ class TestHeatPoller(base.TestCase):
poller.get_version_info = mock.MagicMock() poller.get_version_info = mock.MagicMock()
return (mock_heat_stack, cluster, poller) return (mock_heat_stack, cluster, poller)
def test_poll_and_check_send_notification(self):
mock_heat_stack, cluster, poller = self.setup_poll_test()
mock_heat_stack.stack_status = cluster_status.CREATE_COMPLETE
self.assertIsNone(poller.poll_and_check())
self.assertEqual(mock_heat_stack.stack_status, cluster.status)
mock_heat_stack.stack_status = cluster_status.CREATE_FAILED
self.assertIsNone(poller.poll_and_check())
self.assertEqual(mock_heat_stack.stack_status, cluster.status)
mock_heat_stack.stack_status = cluster_status.DELETE_COMPLETE
self.assertIsNone(poller.poll_and_check())
self.assertEqual(mock_heat_stack.stack_status, cluster.status)
mock_heat_stack.stack_status = cluster_status.DELETE_FAILED
self.assertIsNone(poller.poll_and_check())
self.assertEqual(mock_heat_stack.stack_status, cluster.status)
mock_heat_stack.stack_status = cluster_status.UPDATE_COMPLETE
self.assertIsNone(poller.poll_and_check())
self.assertEqual(mock_heat_stack.stack_status, cluster.status)
mock_heat_stack.stack_status = cluster_status.UPDATE_FAILED
self.assertIsNone(poller.poll_and_check())
self.assertEqual(mock_heat_stack.stack_status, cluster.status)
notifications = fake_notifier.NOTIFICATIONS
self.assertEqual(6, len(notifications))
self.assertEqual(
'magnum.cluster.create', notifications[0].event_type)
self.assertEqual(
taxonomy.OUTCOME_SUCCESS, notifications[0].payload['outcome'])
self.assertEqual(
'magnum.cluster.create', notifications[1].event_type)
self.assertEqual(
taxonomy.OUTCOME_FAILURE, notifications[1].payload['outcome'])
self.assertEqual(
'magnum.cluster.delete', notifications[2].event_type)
self.assertEqual(
taxonomy.OUTCOME_SUCCESS, notifications[2].payload['outcome'])
self.assertEqual(
'magnum.cluster.delete', notifications[3].event_type)
self.assertEqual(
taxonomy.OUTCOME_FAILURE, notifications[3].payload['outcome'])
self.assertEqual(
'magnum.cluster.update', notifications[4].event_type)
self.assertEqual(
taxonomy.OUTCOME_SUCCESS, notifications[4].payload['outcome'])
self.assertEqual(
'magnum.cluster.update', notifications[5].event_type)
self.assertEqual(
taxonomy.OUTCOME_FAILURE, notifications[5].payload['outcome'])
def test_poll_no_save(self): def test_poll_no_save(self):
mock_heat_stack, cluster, poller = self.setup_poll_test() mock_heat_stack, cluster, poller = self.setup_poll_test()
@ -188,11 +138,8 @@ class TestHeatPoller(base.TestCase):
mock_heat_stack.stack_status = cluster_status.DELETE_COMPLETE mock_heat_stack.stack_status = cluster_status.DELETE_COMPLETE
self.assertIsNone(poller.poll_and_check()) self.assertIsNone(poller.poll_and_check())
# The cluster status should still be DELETE_IN_PROGRESS, because # destroy and notifications are handled up the stack now
# the destroy() method may be failed. If success, this cluster record
# will delete directly, change status is meaningless.
self.assertEqual(cluster_status.DELETE_COMPLETE, cluster.status) self.assertEqual(cluster_status.DELETE_COMPLETE, cluster.status)
self.assertEqual(1, cluster.destroy.call_count)
def test_poll_node_count(self): def test_poll_node_count(self):
mock_heat_stack, cluster, poller = self.setup_poll_test() mock_heat_stack, cluster, poller = self.setup_poll_test()
@ -217,11 +164,9 @@ class TestHeatPoller(base.TestCase):
def test_delete_complete(self, cert_manager, trust_manager): def test_delete_complete(self, cert_manager, trust_manager):
mock_heat_stack, cluster, poller = self.setup_poll_test() mock_heat_stack, cluster, poller = self.setup_poll_test()
poller._delete_complete() poller._delete_complete()
self.assertEqual(1, cluster.destroy.call_count)
self.assertEqual( self.assertEqual(
1, cert_manager.delete_certificates_from_cluster.call_count) 1, cert_manager.delete_certificates_from_cluster.call_count)
self.assertEqual(1, self.assertEqual(1, trust_manager.delete_trustee_and_trust.call_count)
trust_manager.delete_trustee_and_trust.call_count)
def test_create_or_complete(self): def test_create_or_complete(self):
mock_heat_stack, cluster, poller = self.setup_poll_test() mock_heat_stack, cluster, poller = self.setup_poll_test()

View File

@ -22,6 +22,7 @@ from magnum import objects
from magnum.objects.fields import ClusterStatus as cluster_status from magnum.objects.fields import ClusterStatus as cluster_status
from magnum.service import periodic from magnum.service import periodic
from magnum.tests import base from magnum.tests import base
from magnum.tests import fake_notifier
from magnum.tests import fakes from magnum.tests import fakes
from magnum.tests.unit.db import utils from magnum.tests.unit.db import utils
@ -151,6 +152,8 @@ class PeriodicTestCase(base.TestCase):
self.assertEqual(cluster_status.ROLLBACK_COMPLETE, self.assertEqual(cluster_status.ROLLBACK_COMPLETE,
self.cluster5.status) self.cluster5.status)
self.assertEqual('fake_reason_55', self.cluster5.status_reason) self.assertEqual('fake_reason_55', self.cluster5.status_reason)
notifications = fake_notifier.NOTIFICATIONS
self.assertEqual(4, len(notifications))
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=fakes.FakeLoopingCall) new=fakes.FakeLoopingCall)
@ -180,6 +183,8 @@ class PeriodicTestCase(base.TestCase):
self.assertEqual(cluster_status.ROLLBACK_IN_PROGRESS, self.assertEqual(cluster_status.ROLLBACK_IN_PROGRESS,
self.cluster5.status) self.cluster5.status)
self.assertEqual('no change', self.cluster5.status_reason) self.assertEqual('no change', self.cluster5.status_reason)
notifications = fake_notifier.NOTIFICATIONS
self.assertEqual(0, len(notifications))
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=fakes.FakeLoopingCall) new=fakes.FakeLoopingCall)
@ -208,6 +213,8 @@ class PeriodicTestCase(base.TestCase):
mock.call(self.cluster4.uuid) mock.call(self.cluster4.uuid)
]) ])
self.assertEqual(2, mock_db_destroy.call_count) self.assertEqual(2, mock_db_destroy.call_count)
notifications = fake_notifier.NOTIFICATIONS
self.assertEqual(5, len(notifications))
@mock.patch('magnum.conductor.monitors.create_monitor') @mock.patch('magnum.conductor.monitors.create_monitor')
@mock.patch('magnum.objects.Cluster.list') @mock.patch('magnum.objects.Cluster.list')