Skip to content

Added 42.scaleout #435

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions samples/42.scaleout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Scale Out

Bot Framework v4 bot Scale Out sample

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.

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.

## Running the sample
- Clone the repository
```bash
git clone https://github.com/Microsoft/botbuilder-python.git
```
- Activate your desired virtual environment
- Bring up a terminal, navigate to `botbuilder-python\samples\42.scaleout` folder
- In the terminal, type `pip install -r requirements.txt`
- In the terminal, type `python app.py`

## Testing the bot using Bot Framework Emulator
[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.

- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)

### Connect to bot using Bot Framework Emulator
- Launch Bot Framework Emulator
- File -> Open Bot
- Paste this URL in the emulator window - http://localhost:3978/api/messages

## Further reading

- [Bot Framework Documentation](https://docs.botframework.com)
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
- [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)
- [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)
- [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
96 changes: 96 additions & 0 deletions samples/42.scaleout/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import asyncio
import sys
from datetime import datetime

from flask import Flask, request, Response
from botbuilder.core import (
BotFrameworkAdapterSettings,
TurnContext,
BotFrameworkAdapter,
)
from botbuilder.schema import Activity, ActivityTypes

from bots import ScaleoutBot

# Create the loop and Flask app
from dialogs import RootDialog
from store import MemoryStore

LOOP = asyncio.get_event_loop()
app = Flask(__name__, instance_relative_config=True)
app.config.from_object("config.DefaultConfig")

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
SETTINGS = BotFrameworkAdapterSettings(app.config["APP_ID"], app.config["APP_PASSWORD"])
ADAPTER = BotFrameworkAdapter(SETTINGS)


# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
# This check writes out errors to console log .vs. app insights.
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)

# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code."
)
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)


ADAPTER.on_turn_error = on_error

# Create the Bot
STORAGE = MemoryStore()
# Use BlobStore to test with Azure Blob storage.
# STORAGE = BlobStore(app.config["BLOB_ACCOUNT_NAME"], app.config["BLOB_KEY"], app.config["BLOB_CONTAINER"])
DIALOG = RootDialog()
BOT = ScaleoutBot(STORAGE, DIALOG)

# Listen for incoming requests on /api/messages
@app.route("/api/messages", methods=["POST"])
def messages():
# Main bot message handler.
if "application/json" in request.headers["Content-Type"]:
body = request.json
else:
return Response(status=415)

activity = Activity().deserialize(body)
auth_header = (
request.headers["Authorization"] if "Authorization" in request.headers else ""
)

try:
task = LOOP.create_task(
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
)
LOOP.run_until_complete(task)
return Response(status=201)
except Exception as exception:
raise exception


if __name__ == "__main__":
try:
app.run(debug=False, port=app.config["PORT"]) # nosec debug
except Exception as exception:
raise exception
6 changes: 6 additions & 0 deletions samples/42.scaleout/bots/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from .scaleout_bot import ScaleoutBot

__all__ = ["ScaleoutBot"]
45 changes: 45 additions & 0 deletions samples/42.scaleout/bots/scaleout_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from botbuilder.core import ActivityHandler, TurnContext
from botbuilder.dialogs import Dialog

from host import DialogHost
from store import Store


class ScaleoutBot(ActivityHandler):
"""
This bot runs Dialogs that send message Activities in a way that can be scaled out with a multi-machine deployment.
The bot logic makes use of the standard HTTP ETag/If-Match mechanism for optimistic locking. This mechanism
is commonly supported on cloud storage technologies from multiple vendors including teh Azure Blob Storage
service. A full implementation against Azure Blob Storage is included in this sample.
"""

def __init__(self, store: Store, dialog: Dialog):
self.store = store
self.dialog = dialog

async def on_message_activity(self, turn_context: TurnContext):
# Create the storage key for this conversation.
key = f"{turn_context.activity.channel_id}/conversations/{turn_context.activity.conversation.id}"

# The execution sits in a loop because there might be a retry if the save operation fails.
while True:
# Load any existing state associated with this key
old_state, e_tag = await self.store.load(key)

# Run the dialog system with the old state and inbound activity, the result is a new state and outbound
# activities.
activities, new_state = await DialogHost.run(
self.dialog, turn_context.activity, old_state
)

# Save the updated state associated with this key.
success = await self.store.save(key, new_state, e_tag)
if success:
if activities:
# This is an actual send on the TurnContext we were given and so will actual do a send this time.
await turn_context.send_activities(activities)

break
18 changes: 18 additions & 0 deletions samples/42.scaleout/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os

""" Bot Configuration """


class DefaultConfig:
""" Bot Configuration """

PORT = 3978
APP_ID = os.environ.get("MicrosoftAppId", "")
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
BLOB_ACCOUNT_NAME = "tboehrestorage"
BLOB_KEY = "A7tc3c9T/n67iDYO7Lx19sTjnA+DD3bR/HQ4yPhJuyVXO1yJ8mYzDOXsBhJrjldh7zKMjE9Wc6PrM1It4nlGPw=="
BLOB_CONTAINER = "dialogs"
Loading