From ad497cecf6a4ba5f29805f1553c5f6a70b758222 Mon Sep 17 00:00:00 2001 From: Oliver Atoa Date: Thu, 24 Jun 2021 20:23:55 +0000 Subject: [PATCH] remove crhelper polllers and improve exception handling - removed crhelper pollers when creating and updating to wor around issue with CloudWatch event payload limit. Build polling is now done in the regular create/update handlers - improved exception handling of subresources to allow cleaner rollbacks after a bot is created but something goes wrong with the locales, intents, slot types, slots --- .../lex_v2_cfn_cr/lambda_function.py | 90 ++++++++----------- .../lex_v2_cfn_cr/lex_v2_cfn_cr/bot.py | 37 +++++--- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/lambda_functions/lex_v2_cfn_cr/lambda_function.py b/src/lambda_functions/lex_v2_cfn_cr/lambda_function.py index cebcb4b..b36dcaf 100644 --- a/src/lambda_functions/lex_v2_cfn_cr/lambda_function.py +++ b/src/lambda_functions/lex_v2_cfn_cr/lambda_function.py @@ -47,30 +47,20 @@ HELPER.init_failure(exception) -@HELPER.poll_create -def poll_create(event, _): - """Poll Create""" - resource_type = event["ResourceType"] - helper_data = event["CrHelperData"] - - if resource_type == "Custom::LexBot": - bot_id = helper_data["botId"] - bot_locale_ids = helper_data["botLocaleIds"] - LEX_CUSTOM_RESOURCE.build_bot_locales(bot_id=bot_id, bot_locale_ids=bot_locale_ids) - - return bot_id - - if resource_type == "Custom::LexBotVersion": - bot_version = helper_data["botVersion"] - - return bot_version - - if resource_type == "Custom::LexBotAlias": - bot_alias_id = helper_data["botAliasId"] - - return bot_alias_id - - raise RuntimeError(f"Invalid resource type: {resource_type}") +def wait_for_bot_locales_build(bot_id, bot_locale_ids): + """Waits for bot locales build""" + # NOTE: not using the cr helper poller functionality since there's a 8K limit + # in the CloudWatch Event input that it uses and medium size bots may trigger + # it during updates as the payload includes JSON encoded current and old + # resource properties + try: + LEX_CUSTOM_RESOURCE.build_bot_locales( + bot_id=bot_id, + bot_locale_ids=bot_locale_ids, + ) + except Exception as exception: # pylint: disable=broad-except + HELPER.Status = "FAILED" + HELPER.Reason = str(exception) @HELPER.create @@ -83,7 +73,18 @@ def create_resource(event, _): response = LEX_CUSTOM_RESOURCE.create_bot(resource_properties=resource_properties) HELPER.Data = response - return response["botId"] + bot_id = response.get("botId") + bot_locale_ids = response.get("botLocaleIds") + _exception = response.get("_exception") + if bot_id and _exception: + # This allows to delete the bot if an exception was raised after + # the bot was created. E.g. while creating the locale, intents, slot, etc. + HELPER.Status = "FAILED" + HELPER.Reason = _exception + else: + wait_for_bot_locales_build(bot_id=bot_id, bot_locale_ids=bot_locale_ids) + + return bot_id if resource_type == "Custom::LexBotVersion": response = LEX_CUSTOM_RESOURCE.create_bot_version(resource_properties=resource_properties) @@ -173,32 +174,6 @@ def delete_resource(event, _): raise RuntimeError(f"Invalid resource type: {resource_type}") -@HELPER.poll_update -def poll_update(event, _): - """Poll Update""" - resource_type = event["ResourceType"] - helper_data = event["CrHelperData"] - - if resource_type == "Custom::LexBot": - bot_id = helper_data["botId"] - bot_locale_ids = helper_data["botLocaleIds"] - LEX_CUSTOM_RESOURCE.build_bot_locales(bot_id=bot_id, bot_locale_ids=bot_locale_ids) - - return bot_id - - if resource_type == "Custom::LexBotVersion": - bot_version = helper_data["botVersion"] - - return bot_version - - if resource_type == "Custom::LexBotAlias": - bot_alias_id = helper_data["botAliasId"] - - return bot_alias_id - - raise RuntimeError(f"Invalid resource type: {resource_type}") - - @HELPER.update def update_resource(event, _): """Update Resource""" @@ -215,7 +190,18 @@ def update_resource(event, _): ) HELPER.Data = response - return response["botId"] + bot_id = response.get("botId") + bot_locale_ids = response.get("botLocaleIds") + _exception = response.get("_exception") + if bot_id and _exception: + # This allows to delete the bot if an exception was raised after + # the bot was created. E.g. while creating the locale, intents, slot, etc. + HELPER.Status = "FAILED" + HELPER.Reason = _exception + else: + wait_for_bot_locales_build(bot_id=bot_id, bot_locale_ids=bot_locale_ids) + + return bot_id if resource_type == "Custom::LexBotVersion": # versions are immutable - a new one is created diff --git a/src/lambda_functions/lex_v2_cfn_cr/lex_v2_cfn_cr/bot.py b/src/lambda_functions/lex_v2_cfn_cr/lex_v2_cfn_cr/bot.py index ee7ea45..7123187 100644 --- a/src/lambda_functions/lex_v2_cfn_cr/lex_v2_cfn_cr/bot.py +++ b/src/lambda_functions/lex_v2_cfn_cr/lex_v2_cfn_cr/bot.py @@ -358,19 +358,25 @@ def build_bot_locales( def create_bot(self, resource_properties: Dict[str, Any]) -> Dict[str, Any]: """Create Lex V2 Bot""" bot_id = self._create_bot(resource_properties=resource_properties) - bot_locales = resource_properties[CUSTOM_ATTRIBUTES["botLocales"]] - self._create_bot_locales( - bot_id=bot_id, - bot_locales=bot_locales, - ) - - bot_locale_ids = [locale["localeId"] for locale in bot_locales] + locale_exeption = {"_exception": ""} + bot_locale_ids = [] + try: + bot_locales = resource_properties[CUSTOM_ATTRIBUTES["botLocales"]] + self._create_bot_locales( + bot_id=bot_id, + bot_locales=bot_locales, + ) + bot_locale_ids = [locale["localeId"] for locale in bot_locales] + except Exception as exception: # pylint: disable=broad-except + self._logger.error("failed to create locale: %s", exception) + locale_exeption["_exception"] = str(exception) return { "botId": bot_id, "botLocaleIds": bot_locale_ids, "lastUpdatedDateTime": datetime.now().isoformat(), + **locale_exeption, } def delete_bot(self, bot_id: str) -> None: @@ -403,16 +409,23 @@ def update_bot( old_resource_properties=old_resource_properties, ) - bot_locale_ids = self._update_bot_locales( - bot_id=_bot_id, - resource_properties=resource_properties, - old_resource_properties=old_resource_properties, - ) + locale_exeption = {"_exception": ""} + bot_locale_ids = [] + try: + bot_locale_ids = self._update_bot_locales( + bot_id=_bot_id, + resource_properties=resource_properties, + old_resource_properties=old_resource_properties, + ) + except Exception as exception: # pylint: disable=broad-except + self._logger.error("failed to create locale: %s", exception) + locale_exeption["_exception"] = str(exception) return { "botId": _bot_id, "botLocaleIds": bot_locale_ids, "lastUpdatedDateTime": datetime.now().isoformat(), + **locale_exeption, } def wait_for_delete_bot(