10000 Added 42.scaleout (#435) · itsmokha/botbuilder-python@3a2e2fa · GitHub
[go: up one dir, main page]

Skip to content

Commit 3a2e2fa

Browse files
tracyboehreraxelsrz
authored andcommitted
Added 42.scaleout (micros 8000 oft#435)
1 parent 0485e63 commit 3a2e2fa

19 files changed

+803
-0
lines changed

samples/42.scaleout/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Scale Out
2+
3+
Bot Framework v4 bot Scale Out sample
4+
5+
This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to use a custom storage solution that supports a deployment scaled out across multiple machines.
6+
7+
The custom storage solution is implemented against memory for testing purposes and against Azure Blob Storage. The sample shows how storage solutions with different policies can be implemented and integrated with the framework. The solution makes use of the standard HTTP ETag/If-Match mechanisms commonly found on cloud storage technologies.
8+
9+
## Running the sample
10+
- Clone the repository
11+
```bash
12+
git clone https://github.com/Microsoft/botbuilder-python.git
13+
```
14+
- Activate your desired virtual environment
15+
- Bring up a terminal, navigate to `botbuilder-python\samples\42.scaleout` folder
16+
- In the terminal, type `pip install -r requirements.txt`
17+
- In the terminal, type `python app.py`
18+
19+
## Testing the bot using Bot Framework Emulator
20+
[Microsoft Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
21+
22+
- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
23+
24+
### Connect to bot using Bot Framework Emulator
25+
- Launch Bot Framework Emulator
26+
- File -> Open Bot
27+
- Paste this URL in the emulator window - http://localhost:3978/api/messages
28+
29+
## Further reading
30+
31+
- [Bot Framework Documentation](https://docs.botframework.com)
32+
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
33+
- [Implementing custom storage for you bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-custom-storage?view=azure-bot-service-4.0)
34+
- [Bot Storage](https://docs.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-state?view=azure-bot-service-3.0&viewFallbackFrom=azure-bot-service-4.0)
35+
- [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)
36+
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)

samples/42.scaleout/app.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import asyncio
5+
import sys
6+
from datetime import datetime
7+
8+
from flask import Flask, request, Response
9+
from botbuilder.core import (
10+
BotFrameworkAdapterSettings,
11+
TurnContext,
12+
BotFrameworkAdapter,
13+
)
14+
from botbuilder.schema import Activity, ActivityTypes
15+
16+
from bots import ScaleoutBot
17+
18+
# Create the loop and Flask app
19+
from dialogs import RootDialog
20+
from store import MemoryStore
21+
22+
LOOP = asyncio.get_event_loop()
23+
app = Flask(__name__, instance_relative_config=True)
24+
app.config.from_object("config.DefaultConfig")
25+
26+
# Create adapter.
27+
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
28+
SETTINGS = BotFrameworkAdapterSettings(app.config["APP_ID"], app.config["APP_PASSWORD"])
29+
ADAPTER = BotFrameworkAdapter(SETTINGS)
30+
31+
32+
# Catch-all for errors.
33+
async def on_error(context: TurnContext, error: Exception):
34+
# This check writes out errors to console log .vs. app insights.
35+
# NOTE: In production environment, you should consider logging this to Azure
36+
# application insights.
37+
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
38+
39+
# Send a message to the user
40+
await context.send_activity("The bot encountered an error or bug.")
41+
await context.send_activity(
42+
"To continue to run this bot, please fix the bot source code."
43+
)
44+
# Send a trace activity if we're talking to the Bot Framework Emulator
45+
if context.activity.channel_id == "emulator":
46+
# Create a trace activity that contains the error object
47+
trace_activity = Activity(
48+
label="TurnError",
49+
name="on_turn_error Trace",
50+
timestamp=datetime.utcnow(),
51+
type=ActivityTypes.trace,
52+
value=f"{error}",
53+
value_type="https://www.botframework.com/schemas/error",
54+
)
55+
# Send a trace activity, which will be displayed in Bot Framework Emulator
56+
await context.send_activity(trace_activity)
57+
58+
59+
ADAPTER.on_turn_error = on_error
60+
61+
# Create the Bot
62+
STORAGE = MemoryS F438 tore()
63+
# Use BlobStore to test with Azure Blob storage.
64+
# STORAGE = BlobStore(app.config["BLOB_ACCOUNT_NAME"], app.config["BLOB_KEY"], app.config["BLOB_CONTAINER"])
65+
DIALOG = RootDialog()
66+
BOT = ScaleoutBot(STORAGE, DIALOG)
67+
68+
# Listen for incoming requests on /api/messages
69+
@app.route("/api/messages", methods=["POST"])
70+
def messages():
71+
# Main bot message handler.
72+
if "application/json" in request.headers["Content-Type"]:
73+
body = request.json
74+
else:
75+
return Response(status=415)
76+
77+
activity = Activity().deserialize(body)
78+
auth_header = (
79+
request.headers["Authorization"] if "Authorization" in request.headers else ""
80+
)
81+
82+
try:
83+
task = LOOP.create_task(
84+
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
85+
)
86+
LOOP.run_until_complete(task)
87+
return Response(status=201)
88+
except Exception as exception:
89+
raise exception
90+
91+
92+
if __name__ == "__main__":
93+
try:
94+
app.run(debug=False, port=app.config["PORT"]) # nosec debug
95+
except Exception as exception:
96+
raise exception

samples/42.scaleout/bots/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .scaleout_bot import ScaleoutBot
5+
6+
__all__ = ["ScaleoutBot"]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.core import ActivityHandler, TurnContext
5+
from botbuilder.dialogs import Dialog
6+
7+
from host import DialogHost
8+
from store import Store
9+
10+
11+
class ScaleoutBot(ActivityHandler):
12+
"""
13+
This bot runs Dialogs that send message Activities in a way that can be scaled out with a multi-machine deployment.
14+
The bot logic makes use of the standard HTTP ETag/If-Match mechanism for optimistic locking. This mechanism
15+
is commonly supported on cloud storage technologies from multiple vendors including teh Azure Blob Storage
16+
service. A full implementation against Azure Blob Storage is included in this sample.
17+
"""
18+
19+
def __init__(self, store: Store, dialog: Dialog):
20+
self.store = store
21+
self.dialog = dialog
22+
23+
async def on_message_activity(self, turn_context: TurnContext):
24+
# Create the storage key for this conversation.
25+
key = f"{turn_context.activity.channel_id}/conversations/{turn_context.activity.conversation.id}"
26+
27+
# The execution sits in a loop because there might be a retry if the save operation fails.
28+
while True:
29+
# Load any existing state associated with this key
30+
old_state, e_tag = await self.store.load(key)
31+
32+
# Run the dialog system with the old state and inbound activity, the result is a new state and outbound
33+
# activities.
34+
activities, new_state = await DialogHost.run(
35+
self.dialog, turn_context.activity, old_state
36+
)
37+
38+
# Save the updated state associated with this key.
39+
success = await self.store.save(key, new_state, e_tag)
40+
if success:
41+
if activities:
42+
# This is an actual send on the TurnContext we were given and so will actual do a send this time.
43+
await turn_context.send_activities(activities)
44+
45+
break

samples/42.scaleout/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License.
4+
5+
import os
6+
7+
""" Bot Configuration """
8+
9+
10+
class DefaultConfig:
11+
""" Bot Configuration """
12+
13+
PORT = 3978
14+
APP_ID = os.environ.get("MicrosoftAppId", "")
15+
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
16+
BLOB_ACCOUNT_NAME = "tboehrestorage"
17+
BLOB_KEY = "A7tc3c9T/n67iDYO7Lx19sTjnA+DD3bR/HQ4yPhJuyVXO1yJ8mYzDOXsBhJrjldh7zKMjE9Wc6PrM1It4nlGPw=="
18+
BLOB_CONTAINER = "dialogs"

0 commit comments

Comments
 (0)
0