Add basic SNS support.

This commit is contained in:
Steve Pulec 2014-05-11 22:56:44 -04:00
commit 216750c05b
10 changed files with 517 additions and 0 deletions

View file

@ -12,6 +12,7 @@ from .iam import mock_iam
from .s3 import mock_s3
from .s3bucket_path import mock_s3bucket_path
from .ses import mock_ses
from .sns import mock_sns
from .sqs import mock_sqs
from .sts import mock_sts
from .route53 import mock_route53

2
moto/sns/__init__.py Normal file
View file

@ -0,0 +1,2 @@
from .models import sns_backend
mock_sns = sns_backend.decorator

155
moto/sns/models.py Normal file
View file

@ -0,0 +1,155 @@
import datetime
import requests
import uuid
from moto.core import BaseBackend
from moto.core.utils import iso_8601_datetime
from moto.sqs.models import sqs_backend
from .utils import make_arn_for_topic, make_arn_for_subscription
DEFAULT_ACCOUNT_ID = 123456789012
class Topic(object):
def __init__(self, name):
self.name = name
self.account_id = DEFAULT_ACCOUNT_ID
self.display_name = ""
self.policy = DEFAULT_TOPIC_POLICY
self.delivery_policy = ""
self.effective_delivery_policy = DEFAULT_EFFECTIVE_DELIVERY_POLICY
self.arn = make_arn_for_topic(self.account_id, name)
self.subscriptions_pending = 0
self.subscriptions_confimed = 0
self.subscriptions_deleted = 0
def publish(self, message):
message_id = unicode(uuid.uuid4())
subscriptions = sns_backend.list_subscriptions(self.arn)
for subscription in subscriptions:
subscription.publish(message, message_id)
return message_id
class Subscription(object):
def __init__(self, topic, endpoint, protocol):
self.topic = topic
self.endpoint = endpoint
self.protocol = protocol
self.arn = make_arn_for_subscription(self.topic.arn)
def publish(self, message, message_id):
if self.protocol == 'sqs':
queue_name = self.endpoint.split(":")[-1]
sqs_backend.send_message(queue_name, message)
elif self.protocol in ['http', 'https']:
post_data = self.get_post_data(message, message_id)
requests.post(self.endpoint, data=post_data)
def get_post_data(self, message, message_id):
return {
"Type": "Notification",
"MessageId": message_id,
"TopicArn": self.topic.arn,
"Subject": "my subject",
"Message": message,
"Timestamp": iso_8601_datetime(datetime.datetime.now()),
"SignatureVersion": "1",
"Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"
}
class SNSBackend(BaseBackend):
def __init__(self):
self.topics = {}
self.subscriptions = {}
def create_topic(self, name):
topic = Topic(name)
self.topics[topic.arn] = topic
return topic
def list_topics(self):
return self.topics.values()
def delete_topic(self, arn):
self.topics.pop(arn)
def get_topic(self, arn):
return self.topics[arn]
def set_topic_attribute(self, topic_arn, attribute_name, attribute_value):
topic = self.get_topic(topic_arn)
setattr(topic, attribute_name, attribute_value)
def subscribe(self, topic_arn, endpoint, protocol):
topic = self.get_topic(topic_arn)
subscription = Subscription(topic, endpoint, protocol)
self.subscriptions[subscription.arn] = subscription
return subscription
def unsubscribe(self, subscription_arn):
self.subscriptions.pop(subscription_arn)
def list_subscriptions(self, topic_arn=None):
if topic_arn:
topic = self.get_topic(topic_arn)
return [sub for sub in self.subscriptions.values() if sub.topic == topic]
else:
return self.subscriptions.values()
def publish(self, topic_arn, message):
topic = self.get_topic(topic_arn)
message_id = topic.publish(message)
return message_id
sns_backend = SNSBackend()
DEFAULT_TOPIC_POLICY = {
"Version": "2008-10-17",
"Id": "us-east-1/698519295917/test__default_policy_ID",
"Statement": [{
"Effect": "Allow",
"Sid": "us-east-1/698519295917/test__default_statement_ID",
"Principal": {
"AWS": "*"
},
"Action": [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish",
"SNS:Receive",
],
"Resource": "arn:aws:sns:us-east-1:698519295917:test",
"Condition": {
"StringLike": {
"AWS:SourceArn": "arn:aws:*:*:698519295917:*"
}
}
}]
}
DEFAULT_EFFECTIVE_DELIVERY_POLICY = {
'http': {
'disableSubscriptionOverrides': False,
'defaultHealthyRetryPolicy': {
'numNoDelayRetries': 0,
'numMinDelayRetries': 0,
'minDelayTarget': 20,
'maxDelayTarget': 20,
'numMaxDelayRetries': 0,
'numRetries': 3,
'backoffFunction': 'linear'
}
}
}

175
moto/sns/responses.py Normal file
View file

@ -0,0 +1,175 @@
import json
from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores
from .models import sns_backend
class SNSResponse(BaseResponse):
def create_topic(self):
name = self._get_param('Name')
topic = sns_backend.create_topic(name)
return json.dumps({
'CreateTopicResponse': {
'CreateTopicResult': {
'TopicArn': topic.arn,
},
'ResponseMetadata': {
'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a',
}
}
})
def list_topics(self):
topics = sns_backend.list_topics()
return json.dumps({
'ListTopicsResponse': {
'ListTopicsResult': {
'Topics': [{'TopicArn': topic.arn} for topic in topics]
}
},
'ResponseMetadata': {
'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a',
}
})
def delete_topic(self):
topic_arn = self._get_param('TopicArn')
sns_backend.delete_topic(topic_arn)
return json.dumps({
'DeleteTopicResponse': {
'ResponseMetadata': {
'RequestId': 'a8dec8b3-33a4-11df-8963-01868b7c937a',
}
}
})
def get_topic_attributes(self):
topic_arn = self._get_param('TopicArn')
topic = sns_backend.get_topic(topic_arn)
return json.dumps({
"GetTopicAttributesResponse": {
"GetTopicAttributesResult": {
"Attributes": {
"Owner": topic.account_id,
"Policy": topic.policy,
"TopicArn": topic.arn,
"DisplayName": topic.display_name,
"SubscriptionsPending": topic.subscriptions_pending,
"SubscriptionsConfirmed": topic.subscriptions_confimed,
"SubscriptionsDeleted": topic.subscriptions_deleted,
"DeliveryPolicy": topic.delivery_policy,
"EffectiveDeliveryPolicy": topic.effective_delivery_policy,
}
},
"ResponseMetadata": {
"RequestId": "057f074c-33a7-11df-9540-99d0768312d3"
}
}
})
def set_topic_attributes(self):
topic_arn = self._get_param('TopicArn')
attribute_name = self._get_param('AttributeName')
attribute_name = camelcase_to_underscores(attribute_name)
attribute_value = self._get_param('AttributeValue')
sns_backend.set_topic_attribute(topic_arn, attribute_name, attribute_value)
return json.dumps({
"SetTopicAttributesResponse": {
"ResponseMetadata": {
"RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042"
}
}
})
def subscribe(self):
topic_arn = self._get_param('TopicArn')
endpoint = self._get_param('Endpoint')
protocol = self._get_param('Protocol')
subscription = sns_backend.subscribe(topic_arn, endpoint, protocol)
return json.dumps({
"SubscribeResponse": {
"SubscribeResult": {
"SubscriptionArn": subscription.arn,
},
"ResponseMetadata": {
"RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042"
}
}
})
def unsubscribe(self):
subscription_arn = self._get_param('SubscriptionArn')
sns_backend.unsubscribe(subscription_arn)
return json.dumps({
"UnsubscribeResponse": {
"ResponseMetadata": {
"RequestId": "a8763b99-33a7-11df-a9b7-05d48da6f042"
}
}
})
def list_subscriptions(self):
subscriptions = sns_backend.list_subscriptions()
return json.dumps({
"ListSubscriptionsResponse": {
"ListSubscriptionsResult": {
"Subscriptions": [{
"TopicArn": subscription.topic.arn,
"Protocol": subscription.protocol,
"SubscriptionArn": subscription.arn,
"Owner": subscription.topic.account_id,
"Endpoint": subscription.endpoint,
} for subscription in subscriptions]
},
"ResponseMetadata": {
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
}
}
})
def list_subscriptions_by_topic(self):
topic_arn = self._get_param('TopicArn')
subscriptions = sns_backend.list_subscriptions(topic_arn)
return json.dumps({
"ListSubscriptionsByTopicResponse": {
"ListSubscriptionsByTopicResult": {
"Subscriptions": [{
"TopicArn": subscription.topic.arn,
"Protocol": subscription.protocol,
"SubscriptionArn": subscription.arn,
"Owner": subscription.topic.account_id,
"Endpoint": subscription.endpoint,
} for subscription in subscriptions]
},
"ResponseMetadata": {
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
}
}
})
def publish(self):
topic_arn = self._get_param('TopicArn')
message = self._get_param('Message')
message_id = sns_backend.publish(topic_arn, message)
return json.dumps({
"PublishResponse": {
"PublishResult": {
"MessageId": message_id,
},
"ResponseMetadata": {
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
}
}
})

9
moto/sns/urls.py Normal file
View file

@ -0,0 +1,9 @@
from .responses import SNSResponse
url_bases = [
"https?://sns.(.+).amazonaws.com"
]
url_paths = {
'{0}/$': SNSResponse().dispatch,
}

10
moto/sns/utils.py Normal file
View file

@ -0,0 +1,10 @@
import uuid
def make_arn_for_topic(account_id, name):
return "arn:aws:sns:us-east-1:{}:{}".format(account_id, name)
def make_arn_for_subscription(topic_arn):
subscription_id = uuid.uuid4()
return "{}:{}".format(topic_arn, subscription_id)