Handle remote thrown NotFound RPC exceptions
This is a short-term fix for a regression caused by If81b68717bd2f9d9be1291c07b18626493c9b5cc which results in undeletable config and deployment resource if the underlying config or deployment no longer exists. Longer term there will be a ClientPlugin for making heat RPC calls which wraps the remote exceptions and raises the original type. The EngineClient methods local_error_name and ignore_error_named will still be useful when this ClientPlugin exists, they just won't be called directly from resources. Change-Id: I8407e04c0404dd6560273d7d74744b5bb7774520
This commit is contained in:
parent
3bc511f354
commit
9822e483ca
|
@ -475,7 +475,8 @@ class Server(stack_user.StackUser):
|
|||
sc = self.rpc_client().show_software_config(
|
||||
self.context, ud_content)
|
||||
return sc[rpc_api.SOFTWARE_CONFIG_CONFIG]
|
||||
except exception.NotFound:
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
return ud_content
|
||||
|
||||
def handle_create(self):
|
||||
|
|
|
@ -16,7 +16,6 @@ from email.mime.multipart import MIMEMultipart
|
|||
from email.mime.text import MIMEText
|
||||
import os
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
|
@ -116,8 +115,8 @@ class MultipartMime(software_config.SoftwareConfig):
|
|||
try:
|
||||
sc = self.rpc_client().show_software_config(
|
||||
self.context, self.resource_id)
|
||||
except exception.NotFound:
|
||||
pass
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
else:
|
||||
part = sc[rpc_api.SOFTWARE_CONFIG_CONFIG]
|
||||
|
||||
|
|
|
@ -135,8 +135,8 @@ class SoftwareComponent(sc.SoftwareConfig):
|
|||
# configs list is stored in 'config' property of parent class
|
||||
# (see handle_create)
|
||||
return sc[rpc_api.SOFTWARE_CONFIG_CONFIG].get(self.CONFIGS)
|
||||
except exception.NotFound:
|
||||
return None
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
|
||||
def validate(self):
|
||||
'''Validate SoftwareComponent properties consistency.'''
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import attributes
|
||||
from heat.engine import constraints
|
||||
|
@ -169,8 +168,8 @@ class SoftwareConfig(resource.Resource):
|
|||
try:
|
||||
self.rpc_client().delete_software_config(
|
||||
self.context, self.resource_id)
|
||||
except exception.NotFound:
|
||||
pass
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
'''
|
||||
|
@ -182,8 +181,8 @@ class SoftwareConfig(resource.Resource):
|
|||
sc = self.rpc_client().show_software_config(
|
||||
self.context, self.resource_id)
|
||||
return sc[rpc_api.SOFTWARE_CONFIG_CONFIG]
|
||||
except exception.NotFound:
|
||||
return None
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
|
|
|
@ -195,8 +195,8 @@ class SoftwareDeployment(signal_responder.SignalResponder):
|
|||
try:
|
||||
self.rpc_client().delete_software_config(
|
||||
self.context, derived_config_id)
|
||||
except exception.NotFound:
|
||||
pass
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
|
||||
def _get_derived_config(self, action, source_config):
|
||||
|
||||
|
@ -391,7 +391,10 @@ class SoftwareDeployment(signal_responder.SignalResponder):
|
|||
|
||||
def handle_delete(self):
|
||||
if self.DELETE in self.properties[self.DEPLOY_ACTIONS]:
|
||||
return self._handle_action(self.DELETE)
|
||||
try:
|
||||
return self._handle_action(self.DELETE)
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
else:
|
||||
self._delete_resource()
|
||||
|
||||
|
@ -417,8 +420,8 @@ class SoftwareDeployment(signal_responder.SignalResponder):
|
|||
derived_config_id = sd[rpc_api.SOFTWARE_DEPLOYMENT_CONFIG_ID]
|
||||
self.rpc_client().delete_software_deployment(
|
||||
self.context, self.resource_id)
|
||||
except exception.NotFound:
|
||||
pass
|
||||
except Exception as ex:
|
||||
self.rpc_client().ignore_error_named(ex, 'NotFound')
|
||||
|
||||
if derived_config_id:
|
||||
self._delete_derived_config(derived_config_id)
|
||||
|
|
|
@ -57,6 +57,25 @@ class EngineClient(object):
|
|||
client = self._client
|
||||
return client.cast(ctxt, method, **kwargs)
|
||||
|
||||
def local_error_name(self, error):
|
||||
"""
|
||||
Returns the name of the error with any _Remote postfix removed.
|
||||
|
||||
:param error: Remote raised error to derive the name from.
|
||||
"""
|
||||
error_name = error.__class__.__name__
|
||||
return error_name.split('_Remote')[0]
|
||||
|
||||
def ignore_error_named(self, error, name):
|
||||
"""
|
||||
Raises the error unless its local name matches the supplied name
|
||||
|
||||
:param error: Remote raised error to derive the local name from.
|
||||
:param name: Name to compare local name to.
|
||||
"""
|
||||
if self.local_error_name(error) != name:
|
||||
raise error
|
||||
|
||||
def identify_stack(self, ctxt, stack_name):
|
||||
"""
|
||||
The identify_stack method returns the full stack identifier for a
|
||||
|
|
|
@ -20,9 +20,11 @@ Unit Tests for heat.rpc.client
|
|||
|
||||
import copy
|
||||
import mock
|
||||
from oslo.messaging._drivers import common as rpc_common
|
||||
import stubout
|
||||
import testtools
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import identifier
|
||||
from heat.common import messaging
|
||||
from heat.rpc import client as rpc_client
|
||||
|
@ -43,6 +45,40 @@ class EngineRpcAPITestCase(testtools.TestCase):
|
|||
self.rpcapi = rpc_client.EngineClient()
|
||||
super(EngineRpcAPITestCase, self).setUp()
|
||||
|
||||
def _to_remote_error(self, error):
|
||||
"""Converts the given exception to the one with the _Remote suffix.
|
||||
"""
|
||||
exc_info = (type(error), error, None)
|
||||
serialized = rpc_common.serialize_remote_exception(exc_info)
|
||||
remote_error = rpc_common.deserialize_remote_exception(
|
||||
serialized, ["heat.common.exception"])
|
||||
return remote_error
|
||||
|
||||
def test_local_error_name(self):
|
||||
ex = exception.NotFound()
|
||||
self.assertEqual('NotFound', self.rpcapi.local_error_name(ex))
|
||||
|
||||
exr = self._to_remote_error(ex)
|
||||
self.assertEqual('NotFound_Remote', exr.__class__.__name__)
|
||||
self.assertEqual('NotFound', self.rpcapi.local_error_name(exr))
|
||||
|
||||
def test_ignore_error_named(self):
|
||||
ex = exception.NotFound()
|
||||
exr = self._to_remote_error(ex)
|
||||
|
||||
self.rpcapi.ignore_error_named(ex, 'NotFound')
|
||||
self.rpcapi.ignore_error_named(exr, 'NotFound')
|
||||
self.assertRaises(
|
||||
exception.NotFound,
|
||||
self.rpcapi.ignore_error_named,
|
||||
ex,
|
||||
'NotSupported')
|
||||
self.assertRaises(
|
||||
exception.NotFound,
|
||||
self.rpcapi.ignore_error_named,
|
||||
exr,
|
||||
'NotSupported')
|
||||
|
||||
def _test_engine_api(self, method, rpc_method, **kwargs):
|
||||
ctxt = utils.dummy_context()
|
||||
expected_retval = 'foo' if method == 'call' else None
|
||||
|
|
Loading…
Reference in New Issue