Лямбда / boto3 / цикл Python

Этот код действует как система раннего предупреждения о сбоях ADFS, которая отлично работает при локальном запуске. Проблема в том, что когда я запускаю его в Lambda, он зацикливается без остановки.

Коротко:

  1. lambda_handler () запускает pagecheck ()
  2. pagecheck () производит необходимую информацию, затем передает 2 списка (msgdet_list, error_list) и int (error_count) в notification ().
  3. notification () сопоставляет и печатает вывод. На выходе получаются две ключевые переменные (заголовок уведомления и тело уведомления).

Я #commentedOut часть SNS, которая обычно отправляет информацию по электронной почте, и я использую print (), чтобы вместо этого отправлять информацию в журналы CloudWatch, пока я не смогу отсортировать цикл. Журналы:

Журналы CloudWatch

Если я запустил это локально, он выдаст чистый одиночный вывод. В Lambda функция будет повторяться, пока не истечет время ожидания. Это почти как каждый раз, когда списки обновляются, они передаются модулю notification (), и он запускается. Я могу ограничить время работы функции, но лучше исправлю код!

Ваше здоровье, такс

# This python/boto3/lambda script sends a request to an Office 365 landing page, parses return details to confirm a successful redirect to /
# the organisation ADFS homepage, authenticates homepge is correct, raises any errors, and sends a consolodated report to /
# an AWS SNS topic.
# Run once to produce pageserver and htmlchar values for global variables.

# Import required modules
import boto3
import urllib.request
from urllib.request import Request, urlopen
from datetime import datetime
import time
import re
import sys

# Global variables to be set
url = "https://outlook.com/CONTOSSO.com"
adfslink = "https://sts.CONTOSSO.com/adfs/ls/?client-request-id = "

# Input after first run
pageserver = "Microsoft-HTTPAPI/2.0 Microsoft-HTTPAPI/2.0"
htmlchar = 18600

# Input AWS SNS ARN
snsarn = 'arn:aws:sns:ap-southeast-2:XXXXXXXXXXXXX:Daily_Check_Notifications_CONTOSSO'
sns = boto3.client('sns')

def pagecheck():
    # Present the request to the webpage as if coming from a user in a browser
    user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
    values = {'name' : 'user'}
    headers = { 'User-Agent' : user_agent }    
    data = urllib.parse.urlencode(values)
    data = data.encode('ascii')

    # "Null" the Message Detail and Error lists
    msgdet_list = []
    error_list = []

    request = Request(url)
    req = urllib.request.Request(url, data, headers)
    response = urlopen(request)

    with urllib.request.urlopen(request) as response:

        # Get the URL. This gets the real URL. 
        acturl = response.geturl()
        msgdet_list.append("\nThe Actual URL is:")
        msgdet_list.append(str(acturl))

        if adfslink not in acturl:
            error_list.append(str("Redirect Fail"))

        # Get the HTTP resonse code
        httpcode = response.code
        msgdet_list.append("\nThe HTTP code is: ")
        msgdet_list.append(str(httpcode))

        if httpcode//200 != 1:
            error_list.append(str("No HTTP 2XX Code"))

        # Get the Headers as a dictionary-like object
        headers = response.info()
        msgdet_list.append("\nThe Headers are:")
        msgdet_list.append(str(headers))

        if response.info() == "":
            error_list.append(str("Header Error"))

        # Get the date of request and compare to UTC (DD MMM YYYY HH MM)
        date = response.info()['date']
        msgdet_list.append("The Date is: ")
        msgdet_list.append(str(date))
        returndate = str(date.split( )[1:5])
        returndate = re.sub(r'[^\w\s]','',returndate)
        returndate = returndate[:-2]
        currentdate = datetime.utcnow()
        currentdate = currentdate.strftime("%d %b %Y %H%M")

        if returndate != currentdate:
            date_error = ("Date Error. Returned Date: ", returndate, "Expected Date: ", currentdate, "Times in UTC (DD MMM YYYY HH MM)")
            date_error = str(date_error)
            date_error = re.sub(r'[^\w\s]','',date_error)
            error_list.append(str(date_error))

        # Get the server
        headerserver = response.info()['server']
        msgdet_list.append("\nThe Server is: ")
        msgdet_list.append(str(headerserver))

        if pageserver not in headerserver:
            error_list.append(str("Server Error"))

        # Get all HTML data and confirm no major change to content size by character lenth (global var: htmlchar).
        html = response.read()
        htmllength = len(html)
        msgdet_list.append("\nHTML Length is: ")
        msgdet_list.append(str(htmllength))
        msgdet_list.append("\nThe Full HTML is: ")
        msgdet_list.append(str(html))
        msgdet_list.append("\n")

        if htmllength // htmlchar != 1:
            error_list.append(str("Page HTML Error - incorrect # of characters"))

        if adfslink not in str(acturl):
            error_list.append(str("ADFS Link Error"))

        error_list.append("\n")
        error_count = len(error_list)

        if error_count == 1:
            error_list.insert(0, 'No Errors Found.')
        elif error_count == 2:
            error_list.insert(0, 'Error Found:')
        else:
            error_list.insert(0, 'Multiple Errors Found:')

        # Pass completed results and data to the notification() module
        notification(msgdet_list, error_list, error_count)

# Use AWS SNS to create a notification email with the additional data generated
def notification(msgdet_list, error_list, errors):
    datacheck = str("\n".join(msgdet_list))
    errorcheck = str("\n".join(error_list))
    notificationbody = str(errorcheck + datacheck)

    if errors >1:
        result = 'FAILED!'
    else:
        result = 'passed.'
    notificationheader = ('The daily ADFS check has been marked as ' + result + ' ' + str(errors) + ' ' + str(error_list))

    if result != 'passed.':

        # message = sns.publish(
        #     TopicArn = snsarn,
        #     Subject = notificationheader,
        #     Message = notificationbody
        # )

        # Output result to CloudWatch logstream
        print('Response: ' + notificationheader)

    else:
        print('passed')

    sys.exit()

# Trigger the Lambda handler
def lambda_handler(event, context):
    aws_account_ids = [context.invoked_function_arn.split(":")[4]]
    pagecheck()
    return "Successful"
    sys.exit()

В вашем коде нет циклического раздела. Какую ошибку вы получаете и каковы настройки лямбда-выражения?

John Hanley 02.08.2018 15:53

Вот что меня беспокоит, циклов нет, но Lambda запускает это снова и снова. Настройки 128 МБ + в любых секундах, до тайм-аута все равно. Ссылка журнала выше показывает ошибки, я попытался удалить все вызовы sys.exit () и попытался добавить их везде, чтобы сломать его, без изменений. Как я уже сказал, отлично работает при локальном запуске, только Lambda зацикливает его.

tacmechmonkey 02.08.2018 17:34

Тайм-аут устанавливается независимо от памяти.

John Hanley 02.08.2018 17:41

Другая идея заключается в том, что AWS Lambda автоматически повторяет попытку вашей функции после сбоя. docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html. Установите максимальное время ожидания лямбда для тестирования (300 секунд).

John Hanley 02.08.2018 21:26

Спасибо, Джон, мне казалось, что Lambda воспринимает sys.exits как неудачные завершения и снова запускает функцию. Для тестирования у меня установлен тайм-аут 15 секунд, чего достаточно, чтобы он зацикливался 3 раза и срабатывал каждую минуту.

tacmechmonkey 03.08.2018 02:04

Хорошо - я думаю, что проблема в sys.exit (). Я не использую sys.exit () в своем лямбда-коде, но я использую при тестировании на своем рабочем столе, поэтому я упустил это из виду. Вы должны выполнить нормальный возврат и регистрировать ошибки только в том случае, если они произошли. Примечание: вам не нужно устанавливать такие жесткие тайм-ауты. Я всегда устанавливаю максимальный тайм-аут в разработке. Поскольку вы выполняете вызовы request (), вы не можете так точно контролировать время отклика сети. Просто используйте таймауты, чтобы остановить цикл while, который будет запускать вечные ошибки.

John Hanley 03.08.2018 02:34

Джон, вы должны поставить ^ в качестве ответа :)

tacmechmonkey 06.08.2018 03:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
1 136
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Журналы CloudWatch содержат следующее сообщение об ошибке:

Process exited before completing request

Это вызвано вызовом sys.exit() в вашем коде. Локально ваш интерпретатор Python просто завершит работу при обнаружении такого sys.exit().

AWS Lambda, с другой стороны, ожидает, что функция Python будет работать только с return, и обрабатывает sys.exit() как ошибку. Поскольку ваша функция наверняка получила вызывается асинхронно AWS Lambda пытается выполнить его дважды.

Чтобы решить вашу проблему, вы можете заменить вхождения sys.exit() на return или даже лучше, просто удалить вызовы sys.exit(), поскольку там уже будут неявные возвраты в тех местах, где вы используете sys.exit().

Эта проблема частично решена: согласно моим подозрениям + комментариям Джона выше, использование sys.exit () заставляло Lambda видеть в функции ошибку, и поэтому она пыталась повторить попытку. Удаление sys.exits сокращает вывод CloudWatch до 1 набора журналов для каждого вызова, однако при введении обмена сообщениями он по-прежнему дважды нажимает на SNS (2 сообщения электронной почты на функцию, если SNS вызывается). Re: время функции, оно в среднем составляет около 6-8 секунд, поэтому время функции 15 секунд не слишком строго контролируется, его достаточно, чтобы просмотреть журналы, созданные 3+ раза, что доказывает ошибку.

tacmechmonkey 06.08.2018 12:05

Учитывая, что обработчик был запущен выражением cron CloudWatch, я думаю, что повторная попытка была результатом непотоковых / синхронных вызовов? Честно говоря, учитывая, что я # прокомментировал статью в соцсети, я поднял это как второй вопрос: stackoverflow.com/questions/51705061/lambda-boto3-python-iss‌ ue

tacmechmonkey 06.08.2018 12:06

Другие вопросы по теме