AWS LambdaからTwilioを使ってエスカレーションを実現する

MMMサーバサイドエンジニアの柳沼です。お世話になっております。
北海道はもうだいぶ寒いです。

弊社ではシステム監視用ミドルウェアDatadogを活用した監視サービスを展開しております。
その中で、異常発生時に電話通知をする必要があるのですが、繋がらなかった場合に次の担当者に電話をしたい、ただし繋がった場合にはそこまでで電話をストップしたいという要件があります。
今回はAWS Lambda上のPython2.7と、Twilioを使ってこれを実現することができたので、やり方を紹介させていただきます。

シンプルに電話をかける

公式の通り、以下のように架電ができます。

1
from twilio.rest import Client

account_sid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
auth_token = "your_auth_token"
client = Client(account_sid, auth_token)
call = client.calls.create(
    to="+81XXXXXXXXX",
    from_="+81ZZZZZZZZZ",
    url="http://demo.twilio.com/docs/voice.xml"
)

これで電話が掛けられます。(account_sidや、tokenは、Twilioに登録した上でコンソールから取得してください。)
Twilioは TwiML という独自マークアップ言語を元に電話を掛けます。(独自マークアップ言語と言っても、形式は普通のXMLです。)
url="http://demo.twilio.com/docs/voice.xml" の部分は、そのTwiMLのURLです。
例えば、以下のような形式になっています。

1
<Response>
  <Say voice="alice">異常事態が発生しました。繰り返します。異常事態が発生しました。</Say>
</Response>

このようなxmlファイルをどこかのサーバに置いて、そのURLをPythonで指定すれば、 <Say> で囲まれた部分を自動音声が喋ってくれます。
今回は、喋る内容を電話ごとに帰る必要があったため、Lambda関数の中でTwiMLを生成し、S3にアップロードし、そのURLを元に架電するようにしています。

TwiMLを生成し、電話をかける

TwiMLの生成は以下のようにできます。

1
import boto3
from twilio.twiml.voice_response import VoiceResponse

AWS_S3_BUCKET_NAME = 'sample_bucket'

# TwiMLの生成
twiml = VoiceResponse()
twiml.say('◯◯ホストで異常事態が発生しました。', language='ja-JP', voice='alice')
filename = 'sample_twiml.xml'

# S3へアップロード
s3 = boto3.client('s3')
s3.put_object(Key=filename, Bucket=AWS_S3_BUCKET_NAME, Body=str(twiml), ContentType='text/xml', ACL='public-read')

# TwiMLのURLを元に電話をかける
url = 'https://s3-ap-northeast-1.amazonaws.com/' + AWS_S3_BUCKET_NAME + '/' + filename
call = client.calls.create(
    to='+81XXXXXXXXX',
    from_='+81ZZZZZZZZZ',
    url=url

S3にアップロードするときはACLをpublic-readにする必要があるため注意してください。

エスカレーションしてみる

エスカレーションは、 call 関数の戻り値を見ることで、通話のステータスを取得し、通話が完了していなければ、次の人に架電する、というやり方をとります。
ステータスは以下のようにとります。

1
call_result = client.calls.create(
    to='+81XXXXXXXXX'
    from_='+81ZZZZZZZZZ'
    url=url,
    method='GET')

sid = call_result.sid
call_status = client.calls(sid).fetch().status
print call_status

最後の call_status が通話のステータスを格納した文字列です。
中の値は公式に載ってます。

これを元に、エスカレーションはこのように実装しました。

1
phone_numbers = ['+81XXXXXXXXX', '+81YYYYYYYYY', '+81ZZZZZZZZZ']
for phone_number in phone_numbers:
    print '{to}への架電を開始します。'.format(to=phone_number)
    call_result = call(phone_number)
    time.sleep(10)
    if call_succeeded(call_result):
        print '{to}への架電に成功しました。'.format(to=phone_number)
        return
    print '{to}への架電に応答がありませんでした。'.format(to=phone_number)
print '全ての架電に応答がありませんでした。'

def call(to):
    call_result = client.calls.create(
        to=to
        from_=TWILIO_PHONE_NUMBER_FROM,
        url=url
        method='GET')
    return call_result

def call_succeeded (call_result):
    sid = call_result.sid
    call_status = client.calls(sid).fetch().status
    if call_status != 'in-progress' and call_status != 'completed':
        # completedにすることで電話が切れる
        client.calls(sid).update(status='completed')
        return False
    return True

実際にテストした所、きちんとエスカレーションがされることを確認できました。

まとめ

Twilioは便利。これからも使っていきます。

このエントリーをはてなブックマークに追加