mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-01-19 02:48:24 +00:00
support AWS SQS as file change notification message queue
This commit is contained in:
parent
200cbcde62
commit
4c97ff3717
|
@ -13,6 +13,7 @@ import (
|
||||||
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/s3sink"
|
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/s3sink"
|
||||||
"github.com/chrislusf/seaweedfs/weed/server"
|
"github.com/chrislusf/seaweedfs/weed/server"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/replication/sub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -37,9 +38,9 @@ func runFilerReplicate(cmd *Command, args []string) bool {
|
||||||
weed_server.LoadConfiguration("replication", true)
|
weed_server.LoadConfiguration("replication", true)
|
||||||
config := viper.GetViper()
|
config := viper.GetViper()
|
||||||
|
|
||||||
var notificationInput replication.NotificationInput
|
var notificationInput sub.NotificationInput
|
||||||
|
|
||||||
for _, input := range replication.NotificationInputs {
|
for _, input := range sub.NotificationInputs {
|
||||||
if config.GetBool("notification." + input.GetName() + ".enabled") {
|
if config.GetBool("notification." + input.GetName() + ".enabled") {
|
||||||
viperSub := config.Sub("notification." + input.GetName())
|
viperSub := config.Sub("notification." + input.GetName())
|
||||||
if err := input.Initialize(viperSub); err != nil {
|
if err := input.Initialize(viperSub); err != nil {
|
||||||
|
@ -99,6 +100,10 @@ func runFilerReplicate(cmd *Command, args []string) bool {
|
||||||
glog.Errorf("receive %s: %+v", key, err)
|
glog.Errorf("receive %s: %+v", key, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if key == "" {
|
||||||
|
// long poll received no messages
|
||||||
|
continue
|
||||||
|
}
|
||||||
if m.OldEntry != nil && m.NewEntry == nil {
|
if m.OldEntry != nil && m.NewEntry == nil {
|
||||||
glog.V(1).Infof("delete: %s", key)
|
glog.V(1).Infof("delete: %s", key)
|
||||||
} else if m.OldEntry == nil && m.NewEntry != nil {
|
} else if m.OldEntry == nil && m.NewEntry != nil {
|
||||||
|
|
|
@ -146,6 +146,14 @@ hosts = [
|
||||||
]
|
]
|
||||||
topic = "seaweedfs_filer"
|
topic = "seaweedfs_filer"
|
||||||
|
|
||||||
|
[notification.aws_sqs]
|
||||||
|
# experimental, let me know if it works
|
||||||
|
enabled = false
|
||||||
|
aws_access_key_id = "" # if empty, loads from the shared credentials file (~/.aws/credentials).
|
||||||
|
aws_secret_access_key = "" # if empty, loads from the shared credentials file (~/.aws/credentials).
|
||||||
|
region = "us-east-2"
|
||||||
|
sqs_queue_name = "my_filer_queue" # an existing queue name
|
||||||
|
|
||||||
`
|
`
|
||||||
REPLICATION_TOML_EXAMPLE = `
|
REPLICATION_TOML_EXAMPLE = `
|
||||||
# A sample TOML config file for replicating SeaweedFS filer
|
# A sample TOML config file for replicating SeaweedFS filer
|
||||||
|
@ -169,6 +177,13 @@ topic = "seaweedfs_filer1_to_filer2"
|
||||||
offsetFile = "./last.offset"
|
offsetFile = "./last.offset"
|
||||||
offsetSaveIntervalSeconds = 10
|
offsetSaveIntervalSeconds = 10
|
||||||
|
|
||||||
|
[notification.aws_sqs]
|
||||||
|
enabled = false
|
||||||
|
aws_access_key_id = "" # if empty, loads from the shared credentials file (~/.aws/credentials).
|
||||||
|
aws_secret_access_key = "" # if empty, loads from the shared credentials file (~/.aws/credentials).
|
||||||
|
region = "us-east-2"
|
||||||
|
sqs_queue_name = "my_filer_queue" # an existing queue name
|
||||||
|
|
||||||
[sink.filer]
|
[sink.filer]
|
||||||
enabled = false
|
enabled = false
|
||||||
grpcAddress = "localhost:18888"
|
grpcAddress = "localhost:18888"
|
||||||
|
|
51
weed/filer2/filer_notify_test.go
Normal file
51
weed/filer2/filer_notify_test.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package filer2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProtoMarshalText(t *testing.T) {
|
||||||
|
|
||||||
|
oldEntry := &Entry{
|
||||||
|
FullPath: FullPath("/this/path/to"),
|
||||||
|
Attr: Attr{
|
||||||
|
Mtime: time.Now(),
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1,
|
||||||
|
Mime: "text/json",
|
||||||
|
TtlSec: 25,
|
||||||
|
},
|
||||||
|
Chunks: []*filer_pb.FileChunk{
|
||||||
|
&filer_pb.FileChunk{
|
||||||
|
FileId: "234,2423423422",
|
||||||
|
Offset: 234234,
|
||||||
|
Size: 234,
|
||||||
|
Mtime: 12312423,
|
||||||
|
ETag: "2342342354",
|
||||||
|
SourceFileId: "23234,2342342342",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
notification := &filer_pb.EventNotification{
|
||||||
|
OldEntry: toProtoEntry(oldEntry),
|
||||||
|
NewEntry: toProtoEntry(nil),
|
||||||
|
DeleteChunks: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
text := proto.MarshalTextString(notification)
|
||||||
|
|
||||||
|
notification2 := &filer_pb.EventNotification{}
|
||||||
|
proto.UnmarshalText(text, notification2)
|
||||||
|
|
||||||
|
if notification2.OldEntry.Chunks[0].SourceFileId != notification.OldEntry.Chunks[0].SourceFileId {
|
||||||
|
t.Fatalf("marshal/unmarshal error: %s", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(text)
|
||||||
|
|
||||||
|
}
|
91
weed/notification/aws_sqs/aws_sqs_pub.go
Normal file
91
weed/notification/aws_sqs/aws_sqs_pub.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package aws_sqs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/notification"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/util"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sqs"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
notification.MessageQueues = append(notification.MessageQueues, &AwsSqsPub{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type AwsSqsPub struct {
|
||||||
|
svc *sqs.SQS
|
||||||
|
queueUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsPub) GetName() string {
|
||||||
|
return "aws_sqs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsPub) Initialize(configuration util.Configuration) (err error) {
|
||||||
|
glog.V(0).Infof("filer.notification.aws_sqs.region: %v", configuration.GetString("region"))
|
||||||
|
glog.V(0).Infof("filer.notification.aws_sqs.sqs_queue_name: %v", configuration.GetString("sqs_queue_name"))
|
||||||
|
return k.initialize(
|
||||||
|
configuration.GetString("aws_access_key_id"),
|
||||||
|
configuration.GetString("aws_secret_access_key"),
|
||||||
|
configuration.GetString("region"),
|
||||||
|
configuration.GetString("sqs_queue_name"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsPub) initialize(awsAccessKeyId, aswSecretAccessKey, region, queueName string) (err error) {
|
||||||
|
|
||||||
|
config := &aws.Config{
|
||||||
|
Region: aws.String(region),
|
||||||
|
}
|
||||||
|
if awsAccessKeyId != "" && aswSecretAccessKey != "" {
|
||||||
|
config.Credentials = credentials.NewStaticCredentials(awsAccessKeyId, aswSecretAccessKey, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := session.NewSession(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create aws session: %v", err)
|
||||||
|
}
|
||||||
|
k.svc = sqs.New(sess)
|
||||||
|
|
||||||
|
result, err := k.svc.GetQueueUrl(&sqs.GetQueueUrlInput{
|
||||||
|
QueueName: aws.String(queueName),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == sqs.ErrCodeQueueDoesNotExist {
|
||||||
|
return fmt.Errorf("unable to find queue %s", queueName)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("get queue %s url: %v", queueName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k.queueUrl = *result.QueueUrl
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsPub) SendMessage(key string, message proto.Message) (err error) {
|
||||||
|
|
||||||
|
text := proto.MarshalTextString(message)
|
||||||
|
|
||||||
|
_, err = k.svc.SendMessage(&sqs.SendMessageInput{
|
||||||
|
DelaySeconds: aws.Int64(10),
|
||||||
|
MessageAttributes: map[string]*sqs.MessageAttributeValue{
|
||||||
|
"key": &sqs.MessageAttributeValue{
|
||||||
|
DataType: aws.String("String"),
|
||||||
|
StringValue: aws.String(key),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MessageBody: aws.String(text),
|
||||||
|
QueueUrl: &k.queueUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("send message to sqs %s: %v", k.queueUrl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/chrislusf/seaweedfs/weed/replication/sink"
|
"github.com/chrislusf/seaweedfs/weed/replication/sink"
|
||||||
"github.com/chrislusf/seaweedfs/weed/replication/source"
|
"github.com/chrislusf/seaweedfs/weed/replication/source"
|
||||||
"github.com/chrislusf/seaweedfs/weed/util"
|
"github.com/chrislusf/seaweedfs/weed/util"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type S3Sink struct {
|
type S3Sink struct {
|
||||||
|
@ -37,6 +38,9 @@ func (s3sink *S3Sink) GetSinkToDirectory() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3sink *S3Sink) Initialize(configuration util.Configuration) error {
|
func (s3sink *S3Sink) Initialize(configuration util.Configuration) error {
|
||||||
|
glog.V(0).Infof("sink.s3.region: %v", configuration.GetString("region"))
|
||||||
|
glog.V(0).Infof("sink.s3.bucket: %v", configuration.GetString("bucket"))
|
||||||
|
glog.V(0).Infof("sink.s3.directory: %v", configuration.GetString("directory"))
|
||||||
return s3sink.initialize(
|
return s3sink.initialize(
|
||||||
configuration.GetString("aws_access_key_id"),
|
configuration.GetString("aws_access_key_id"),
|
||||||
configuration.GetString("aws_secret_access_key"),
|
configuration.GetString("aws_secret_access_key"),
|
||||||
|
|
111
weed/replication/sub/notification_aws_sqs.go
Normal file
111
weed/replication/sub/notification_aws_sqs.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/util"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/sqs"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
NotificationInputs = append(NotificationInputs, &AwsSqsInput{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type AwsSqsInput struct {
|
||||||
|
svc *sqs.SQS
|
||||||
|
queueUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsInput) GetName() string {
|
||||||
|
return "aws_sqs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsInput) Initialize(configuration util.Configuration) error {
|
||||||
|
glog.V(0).Infof("replication.notification.aws_sqs.region: %v", configuration.GetString("region"))
|
||||||
|
glog.V(0).Infof("replication.notification.aws_sqs.sqs_queue_name: %v", configuration.GetString("sqs_queue_name"))
|
||||||
|
return k.initialize(
|
||||||
|
configuration.GetString("aws_access_key_id"),
|
||||||
|
configuration.GetString("aws_secret_access_key"),
|
||||||
|
configuration.GetString("region"),
|
||||||
|
configuration.GetString("sqs_queue_name"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsInput) initialize(awsAccessKeyId, aswSecretAccessKey, region, queueName string) (err error) {
|
||||||
|
|
||||||
|
config := &aws.Config{
|
||||||
|
Region: aws.String(region),
|
||||||
|
}
|
||||||
|
if awsAccessKeyId != "" && aswSecretAccessKey != "" {
|
||||||
|
config.Credentials = credentials.NewStaticCredentials(awsAccessKeyId, aswSecretAccessKey, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := session.NewSession(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create aws session: %v", err)
|
||||||
|
}
|
||||||
|
k.svc = sqs.New(sess)
|
||||||
|
|
||||||
|
result, err := k.svc.GetQueueUrl(&sqs.GetQueueUrlInput{
|
||||||
|
QueueName: aws.String(queueName),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == sqs.ErrCodeQueueDoesNotExist {
|
||||||
|
return fmt.Errorf("unable to find queue %s", queueName)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("get queue %s url: %v", queueName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k.queueUrl = *result.QueueUrl
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *AwsSqsInput) ReceiveMessage() (key string, message *filer_pb.EventNotification, err error) {
|
||||||
|
|
||||||
|
// receive message
|
||||||
|
result, err := k.svc.ReceiveMessage(&sqs.ReceiveMessageInput{
|
||||||
|
AttributeNames: []*string{
|
||||||
|
aws.String(sqs.MessageSystemAttributeNameSentTimestamp),
|
||||||
|
},
|
||||||
|
MessageAttributeNames: []*string{
|
||||||
|
aws.String(sqs.QueueAttributeNameAll),
|
||||||
|
},
|
||||||
|
QueueUrl: &k.queueUrl,
|
||||||
|
MaxNumberOfMessages: aws.Int64(1),
|
||||||
|
VisibilityTimeout: aws.Int64(20), // 20 seconds
|
||||||
|
WaitTimeSeconds: aws.Int64(20),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("receive message from sqs %s: %v", k.queueUrl, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(result.Messages) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the message
|
||||||
|
key = *result.Messages[0].Attributes["key"]
|
||||||
|
text := *result.Messages[0].Body
|
||||||
|
message = &filer_pb.EventNotification{}
|
||||||
|
err = proto.UnmarshalText(text, message)
|
||||||
|
|
||||||
|
// delete the message
|
||||||
|
_, err = k.svc.DeleteMessage(&sqs.DeleteMessageInput{
|
||||||
|
QueueUrl: &k.queueUrl,
|
||||||
|
ReceiptHandle: result.Messages[0].ReceiptHandle,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
glog.V(1).Infof("delete message from sqs %s: %v", k.queueUrl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package replication
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
||||||
package replication
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
"github.com/chrislusf/seaweedfs/weed/notification"
|
"github.com/chrislusf/seaweedfs/weed/notification"
|
||||||
_ "github.com/chrislusf/seaweedfs/weed/notification/kafka"
|
_ "github.com/chrislusf/seaweedfs/weed/notification/kafka"
|
||||||
|
_ "github.com/chrislusf/seaweedfs/weed/notification/aws_sqs"
|
||||||
_ "github.com/chrislusf/seaweedfs/weed/notification/log"
|
_ "github.com/chrislusf/seaweedfs/weed/notification/log"
|
||||||
"github.com/chrislusf/seaweedfs/weed/security"
|
"github.com/chrislusf/seaweedfs/weed/security"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
Loading…
Reference in a new issue