@ -12,16 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
from heatclient import exc as heat_exc
import mock
from magnum . common import context
from magnum . common . rpc_service import CONF
from magnum . db . sqlalchemy import api as dbapi
from magnum . drivers . common import driver
from magnum import objects
from magnum . objects . fields import ClusterStatus as cluster_status
from magnum . service import periodic
from magnum . tests import base
from magnum . tests import fakes
from magnum . tests . unit . db import utils
@ -36,7 +37,7 @@ class PeriodicTestCase(base.TestCase):
def setUp ( self ) :
super ( PeriodicTestCase , self ) . setUp ( )
c tx = context . make_admin_context ( )
self . con te xt = context . make_admin_context ( )
# Can be identical for all clusters.
trust_attrs = {
@ -46,165 +47,167 @@ class PeriodicTestCase(base.TestCase):
}
trust_attrs . update ( { ' id ' : 1 , ' stack_id ' : ' 11 ' ,
' status ' : cluster_status . CREATE_IN_PROGRESS } )
' status ' : cluster_status . CREATE_IN_PROGRESS ,
' status_reason ' : ' no change ' } )
cluster1 = utils . get_test_cluster ( * * trust_attrs )
trust_attrs . update ( { ' id ' : 2 , ' stack_id ' : ' 22 ' ,
' status ' : cluster_status . DELETE_IN_PROGRESS } )
' status ' : cluster_status . DELETE_IN_PROGRESS ,
' status_reason ' : ' no change ' } )
cluster2 = utils . get_test_cluster ( * * trust_attrs )
trust_attrs . update ( { ' id ' : 3 , ' stack_id ' : ' 33 ' ,
' status ' : cluster_status . UPDATE_IN_PROGRESS } )
' status ' : cluster_status . UPDATE_IN_PROGRESS ,
' status_reason ' : ' no change ' } )
cluster3 = utils . get_test_cluster ( * * trust_attrs )
trust_attrs . update ( { ' id ' : 4 , ' stack_id ' : ' 44 ' ,
' status ' : cluster_status . CREATE_COMPLETE } )
' status ' : cluster_status . DELETE_IN_PROGRESS ,
' status_reason ' : ' no change ' } )
cluster4 = utils . get_test_cluster ( * * trust_attrs )
trust_attrs . update ( { ' id ' : 5 , ' stack_id ' : ' 55 ' ,
' status ' : cluster_status . ROLLBACK_IN_PROGRESS } )
' status ' : cluster_status . ROLLBACK_IN_PROGRESS ,
' status_reason ' : ' no change ' } )
cluster5 = utils . get_test_cluster ( * * trust_attrs )
self . cluster1 = objects . Cluster ( c tx, * * cluster1 )
self . cluster2 = objects . Cluster ( c tx, * * cluster2 )
self . cluster3 = objects . Cluster ( c tx, * * cluster3 )
self . cluster4 = objects . Cluster ( c tx, * * cluster4 )
self . cluster5 = objects . Cluster ( c tx, * * cluster5 )
@mock . patch . object ( objects . Cluster , ' list ' )
@mock . patch ( ' magnum.common.clients.OpenStackClients ' )
@mock . patch . object ( dbapi . Connection , ' destroy_cluster ' )
@mock . patch . object ( dbapi . Connection , ' update_cluster ' )
def test_sync_cluster_status_changes ( self , mock_db_update , mock_db_destroy ,
mock_oscc , mock_cluster_list ) :
mock_heat_client = mock . MagicMock ( )
stack1 = fake_stack (
self . cluster1 = objects . Cluster ( self . con te xt , * * cluster1 )
self . cluster2 = objects . Cluster ( self . con te xt , * * cluster2 )
self . cluster3 = objects . Cluster ( self . con te xt , * * cluster3 )
self . cluster4 = objects . Cluster ( self . con te xt , * * cluster4 )
self . cluster5 = objects . Cluster ( self . con te xt , * * cluster5 )
# these tests are based on the basic behavior of our standard
# Heat-based drivers, but drivers based on other orchestration
# methods should generally behave in a similar fashion as far
# as the actual calls go. It is up to the driver implementor
# to ensure their implementation of update_cluster_status behaves
# as expected regardless of how the periodic updater task works
self . mock_heat_client = mock . MagicMock ( )
self . stack1 = fake_stack (
id = ' 11 ' , stack_status = cluster_status . CREATE_COMPLETE ,
stack_status_reason = ' fake_reason_11 ' )
stack3 = fake_stack (
self . stack2 = fake_stack (
id = ' 22 ' , stack_status = cluster_status . DELETE_IN_PROGRESS ,
stack_status_reason = ' fake_reason_11 ' )
self . stack3 = fake_stack (
id = ' 33 ' , stack_status = cluster_status . UPDATE_COMPLETE ,
stack_status_reason = ' fake_reason_33 ' )
stack5 = fake_stack (
self . stack5 = fake_stack (
id = ' 55 ' , stack_status = cluster_status . ROLLBACK_COMPLETE ,
stack_status_reason = ' fake_reason_55 ' )
mock_heat_client . stacks . list . return_value = [ stack1 , stack3 , stack5 ]
get_stacks = { ' 11 ' : stack1 , ' 33 ' : stack3 , ' 55 ' : stack5 }
self . mock_heat_client . stacks . list . return_value = [
self . stack1 , self . stack2 , self . stack3 , self . stack5 ]
self . get_stacks = {
' 11 ' : self . stack1 ,
' 22 ' : self . stack2 ,
' 33 ' : self . stack3 ,
' 55 ' : self . stack5
}
def stack_get_sideefect ( arg ) :
if arg == ' 22 ' :
raise heat_exc . HTTPNotFound
return get_stacks [ arg ]
self . mock_driver = mock . MagicMock ( spec = driver . Driver )
def _mock_update_status ( context , cluster ) :
try :
stack = self . get_stacks [ cluster . stack_id ]
except KeyError :
cluster . status_reason = " Stack %s not found " % cluster . stack_id
if cluster . status == " DELETE_IN_PROGRESS " :
cluster . status = cluster_status . DELETE_COMPLETE
else :
cluster . status = cluster . status . replace ( " IN_PROGRESS " ,
" FAILED " )
cluster . status = cluster . status . replace ( " COMPLETE " ,
" FAILED " )
else :
if cluster . status != stack . stack_status :
cluster . status = stack . stack_status
cluster . status_reason = stack . stack_status_reason
self . mock_driver . update_cluster_status . side_effect = (
_mock_update_status )
@mock . patch ( ' oslo_service.loopingcall.FixedIntervalLoopingCall ' ,
new = fakes . FakeLoopingCall )
@mock . patch ( ' magnum.drivers.common.driver.Driver.get_driver_for_cluster ' )
@mock . patch ( ' magnum.objects.Cluster.list ' )
@mock . patch . object ( dbapi . Connection , ' destroy_cluster ' )
def test_sync_cluster_status_changes ( self , mock_db_destroy ,
mock_cluster_list , mock_get_driver ) :
mock_heat_client . stacks . get . side_effect = stack_get_sideefect
mock_osc = mock_oscc . return_value
mock_osc . heat . return_value = mock_heat_client
mock_cluster_list . return_value = [ self . cluster1 , self . cluster2 ,
self . cluster3 , self . cluster5 ]
mock_keystone_client = mock . MagicMock ( )
mock_keystone_client . client . project_id = " fake_project "
mock_osc . keystone . return_value = mock_keystone_client
self . cluster3 , self . cluster4 ,
self . cluster5 ]
mock_get_driver . return_value = self . mock_driver
periodic . MagnumPeriodicTasks ( CONF ) . sync_cluster_status ( None )
self . assertEqual ( cluster_status . CREATE_COMPLETE , self . cluster1 . status )
self . assertEqual ( ' fake_reason_11 ' , self . cluster1 . status_reason )
mock_db_destroy . assert_called_once_with ( self . cluster2 . uuid )
# make sure cluster 2 didn't change
self . assertEqual ( cluster_status . DELETE_IN_PROGRESS ,
self . cluster2 . status )
self . assertEqual ( ' no change ' , self . cluster2 . status_reason )
self . assertEqual ( cluster_status . UPDATE_COMPLETE , self . cluster3 . status )
self . assertEqual ( ' fake_reason_33 ' , self . cluster3 . status_reason )
mock_db_destroy . assert_called_once_with ( self . cluster4 . uuid )
self . assertEqual ( cluster_status . ROLLBACK_COMPLETE ,
self . cluster5 . status )
self . assertEqual ( ' fake_reason_55 ' , self . cluster5 . status_reason )
@mock . patch . object ( objects . Cluster , ' list ' )
@mock . patch ( ' magnum.common.clients.OpenStackClients ' )
def test_sync_auth_fail ( self , mock_oscc , mock_cluster_list ) :
""" Tests handling for unexpected exceptions in _get_cluster_stacks()
It does this by raising an a HTTPUnauthorized exception in Heat client .
The affected stack thus missing from the stack list should not lead to
cluster state changing in this case . Likewise , subsequent clusters
should still change state , despite the affected cluster being skipped .
"""
stack1 = fake_stack ( id = ' 11 ' ,
stack_status = cluster_status . CREATE_COMPLETE )
mock_heat_client = mock . MagicMock ( )
def stack_get_sideefect ( arg ) :
raise heat_exc . HTTPUnauthorized
mock_heat_client . stacks . get . side_effect = stack_get_sideefect
mock_heat_client . stacks . list . return_value = [ stack1 ]
mock_osc = mock_oscc . return_value
mock_osc . heat . return_value = mock_heat_client
mock_cluster_list . return_value = [ self . cluster1 ]
periodic . MagnumPeriodicTasks ( CONF ) . sync_cluster_status ( None )
self . assertEqual ( cluster_status . CREATE_IN_PROGRESS ,
self . cluster1 . status )
@mock . patch ( ' oslo_service.loopingcall.FixedIntervalLoopingCall ' ,
new = fakes . FakeLoopingCall )
@mock . patch ( ' magnum.drivers.common.driver.Driver.get_driver_for_cluster ' )
@mock . patch ( ' magnum.objects.Cluster.list ' )
def test_sync_cluster_status_not_changes ( self , mock_cluster_list ,
mock_get_driver ) :
@mock . patch . object ( objects . Cluster , ' list ' )
@mock . patch ( ' magnum.common.clients.OpenStackClients ' )
def test_sync_cluster_status_not_changes ( self , mock_oscc ,
mock_cluster_list ) :
mock_heat_client = mock . MagicMock ( )
stack1 = fake_stack ( id = ' 11 ' ,
stack_status = cluster_status . CREATE_IN_PROGRESS )
stack2 = fake_stack ( id = ' 22 ' ,
stack_status = cluster_status . DELETE_IN_PROGRESS )
stack3 = fake_stack ( id = ' 33 ' ,
stack_status = cluster_status . UPDATE_IN_PROGRESS )
stack5 = fake_stack ( id = ' 55 ' ,
stack_status = cluster_status . ROLLBACK_IN_PROGRESS )
get_stacks = { ' 11 ' : stack1 , ' 22 ' : stack2 , ' 33 ' : stack3 , ' 55 ' : stack5 }
def stack_get_sideefect ( arg ) :
if arg == ' 22 ' :
raise heat_exc . HTTPNotFound
return get_stacks [ arg ]
mock_heat_client . stacks . get . side_effect = stack_get_sideefect
mock_heat_client . stacks . list . return_value = [ stack1 , stack2 , stack3 ,
stack5 ]
mock_osc = mock_oscc . return_value
mock_osc . heat . return_value = mock_heat_client
self . stack1 . stack_status = self . cluster1 . status
self . stack2 . stack_status = self . cluster2 . status
self . stack3 . stack_status = self . cluster3 . status
self . stack5 . stack_status = self . cluster5 . status
mock_cluster_list . return_value = [ self . cluster1 , self . cluster2 ,
self . cluster3 , self . cluster5 ]
mock_get_driver . return_value = self . mock_driver
periodic . MagnumPeriodicTasks ( CONF ) . sync_cluster_status ( None )
self . assertEqual ( cluster_status . CREATE_IN_PROGRESS ,
self . cluster1 . status )
self . assertEqual ( ' no change ' , self . cluster1 . status_reason )
self . assertEqual ( cluster_status . DELETE_IN_PROGRESS ,
self . cluster2 . status )
self . assertEqual ( ' no change ' , self . cluster2 . status_reason )
self . assertEqual ( cluster_status . UPDATE_IN_PROGRESS ,
self . cluster3 . status )
self . assertEqual ( ' no change ' , self . cluster3 . status_reason )
self . assertEqual ( cluster_status . ROLLBACK_IN_PROGRESS ,
self . cluster5 . status )
self . assertEqual ( ' no change ' , self . cluster5 . status_reason )
@mock . patch . object ( objects . Cluster , ' list ' )
@mock . patch ( ' magnum.common.clients.OpenStackClients ' )
@mock . patch ( ' oslo_service.loopingcall.FixedIntervalLoopingCall ' ,
new = fakes . FakeLoopingCall )
@mock . patch ( ' magnum.drivers.common.driver.Driver.get_driver_for_cluster ' )
@mock . patch ( ' magnum.objects.Cluster.list ' )
@mock . patch . object ( dbapi . Connection , ' destroy_cluster ' )
@mock . patch . object ( dbapi . Connection , ' update_cluster ' )
def test_sync_cluster_status_heat_not_found ( self , mock_db_update ,
mock_db_destroy , mock_oscc ,
mock_cluster_list ) :
mock_heat_client = mock . MagicMock ( )
mock_heat_client . stacks . list . return_value = [ ]
mock_osc = mock_oscc . return_value
mock_osc . heat . return_value = mock_heat_client
def test_sync_cluster_status_heat_not_found ( self , mock_db_destroy ,
mock_cluster_list ,
mock_get_driver ) :
self . get_stacks . clear ( )
mock_get_driver . return_value = self . mock_driver
mock_cluster_list . return_value = [ self . cluster1 , self . cluster2 ,
self . cluster3 ]
mock_keystone_client = mock . MagicMock ( )
mock_keystone_client . client . project_id = " fake_project "
mock_osc . keystone . return_value = mock_keystone_client
self . cluster3 , self . cluster4 ,
self . cluster5 ]
periodic . MagnumPeriodicTasks ( CONF ) . sync_cluster_status ( None )
self . assertEqual ( cluster_status . CREATE_FAILED , self . cluster1 . status )
self . assertEqual ( ' Stack with id 11 not found in Heat. ' ,
self . cluster1 . status_reason )
mock_db_destroy . assert_called_once_with ( self . cluster2 . uuid )
self . assertEqual ( ' Stack 11 not found ' , self . cluster1 . status_reason )
self . assertEqual ( cluster_status . UPDATE_FAILED , self . cluster3 . status )
self . assertEqual ( ' Stack with id 33 not found in Heat. ' ,
self . cluster3 . status_reason )
self . assertEqual ( ' Stack 33 not found ' , self . cluster3 . status_reason )
self . assertEqual ( cluster_status . ROLLBACK_FAILED , self . cluster5 . status )
self . assertEqual ( ' Stack 55 not found ' , self . cluster5 . status_reason )
mock_db_destroy . assert_has_calls ( [
mock . call ( self . cluster2 . uuid ) ,
mock . call ( self . cluster4 . uuid )
] )
self . assertEqual ( 2 , mock_db_destroy . call_count )
@mock . patch ( ' magnum.conductor.monitors.create_monitor ' )
@mock . patch ( ' magnum.objects.Cluster.list ' )
@ -219,6 +222,7 @@ class PeriodicTestCase(base.TestCase):
mock_get_notifier . return_value = notifier
mock_cluster_list . return_value = [ self . cluster1 , self . cluster2 ,
self . cluster3 , self . cluster4 ]
self . cluster4 . status = cluster_status . CREATE_COMPLETE
monitor = mock . MagicMock ( )
monitor . get_metric_names . return_value = [ ' metric1 ' , ' metric2 ' ]
monitor . compute_metric_value . return_value = 30
@ -262,6 +266,7 @@ class PeriodicTestCase(base.TestCase):
notifier = mock . MagicMock ( )
mock_get_notifier . return_value = notifier
mock_cluster_list . return_value = [ self . cluster4 ]
self . cluster4 . status = cluster_status . CREATE_COMPLETE
monitor = mock . MagicMock ( )
monitor . get_metric_names . return_value = [ ' metric1 ' , ' metric2 ' ]
monitor . compute_metric_value . side_effect = Exception (
@ -292,6 +297,7 @@ class PeriodicTestCase(base.TestCase):
notifier = mock . MagicMock ( )
mock_get_notifier . return_value = notifier
mock_cluster_list . return_value = [ self . cluster4 ]
self . cluster4 . status = cluster_status . CREATE_COMPLETE
monitor = mock . MagicMock ( )
monitor . pull_data . side_effect = Exception ( " error on pulling data " )
mock_create_monitor . return_value = monitor
@ -312,6 +318,7 @@ class PeriodicTestCase(base.TestCase):
notifier = mock . MagicMock ( )
mock_get_notifier . return_value = notifier
mock_cluster_list . return_value = [ self . cluster4 ]
self . cluster4 . status = cluster_status . CREATE_COMPLETE
mock_create_monitor . return_value = None
periodic . MagnumPeriodicTasks ( CONF ) . _send_cluster_metrics ( self . context )