Kafka integration (#831)
Kafka integration with Fission enables invoking a function when a message arrives in a Kafka topic
This commit is contained in:
+10
-5
@@ -56,11 +56,16 @@ The following table lists the configurable parameters of the Fission chart and t
|
|||||||
| `logger.influxdbAdmin` | Log database admin username | `admin` |
|
| `logger.influxdbAdmin` | Log database admin username | `admin` |
|
||||||
| `logger.fluentdImage` | Logger fluentd image | `fission/fluentd` |
|
| `logger.fluentdImage` | Logger fluentd image | `fission/fluentd` |
|
||||||
| `fissionUiImage` | Fission ui image | `fission/fission-ui:0.1.0` |
|
| `fissionUiImage` | Fission ui image | `fission/fission-ui:0.1.0` |
|
||||||
| `messageQueue` | Message queue type | `nats-streaming` |
|
| `nats.enabled` | Nats streaming enabled | `true` |
|
||||||
| `nats.authToken` | Nats streaming auth token | `defaultFissionAuthToken` |
|
| `nats.authToken` | Nats streaming auth token | `defaultFissionAuthToken`(required if `nats.enabled` is `true`) |
|
||||||
| `nats.clusterID` | Nats streaming clusterID | `fissionMQTrigger` |
|
| `nats.clusterID` | Nats streaming clusterID | `fissionMQTrigger`(required if `nats.enabled` is `true`) |
|
||||||
| `azureStorageQueue.accountName` | Azure storage account name | None (required if `messageQueue` is `azure-storage-queue`) |
|
| `azureStorageQueue.enabled` * | Azure storage account name | false |
|
||||||
| `azureStorageQueue.key` | Azure storage access key | None (required if `messageQueue` is `azure-storage-queue`) |
|
| `azureStorageQueue.accountName` | Azure storage account name | None (required if `azureStorageQueue.enabled` is `true`) |
|
||||||
|
| `azureStorageQueue.key` | Azure storage access key | None (required if `azureStorageQueue.enabled` is `true`) |
|
||||||
|
| `kafka.enabled` * | Kafka trigger enabled | `false` |
|
||||||
|
| `kafka.brokers` | Kafka brokers uri | `broker.kafka:9092` (required if `kafka.enabled` is `true`) |
|
||||||
|
|
||||||
|
* - Please note that deploying of Azure Storage Queue or Kafka is not done by Fission chart and you will have to explicitly deploy them.
|
||||||
|
|
||||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
||||||
|
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ spec:
|
|||||||
# serviceAccount: fission-svc
|
# serviceAccount: fission-svc
|
||||||
|
|
||||||
---
|
---
|
||||||
{{- if eq .Values.messageQueue.type "nats-streaming" }}
|
{{- if .Values.nats.enabled }}
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -553,17 +553,16 @@ spec:
|
|||||||
"--auth", "{{ .Values.nats.authToken }}",
|
"--auth", "{{ .Values.nats.authToken }}",
|
||||||
"--max_channels", "0"
|
"--max_channels", "0"
|
||||||
]
|
]
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 4222
|
- containerPort: 4222
|
||||||
hostPort: 4222
|
hostPort: 4222
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: mqtrigger
|
name: mqtrigger-nats-streaming
|
||||||
labels:
|
labels:
|
||||||
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
spec:
|
spec:
|
||||||
@@ -572,6 +571,7 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
svc: mqtrigger
|
svc: mqtrigger
|
||||||
|
messagequeue: nats-streaming
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: mqtrigger
|
- name: mqtrigger
|
||||||
@@ -581,11 +581,65 @@ spec:
|
|||||||
args: ["--mqt", "--routerUrl", "http://router.{{ .Release.Namespace }}"]
|
args: ["--mqt", "--routerUrl", "http://router.{{ .Release.Namespace }}"]
|
||||||
env:
|
env:
|
||||||
- name: MESSAGE_QUEUE_TYPE
|
- name: MESSAGE_QUEUE_TYPE
|
||||||
value: {{ .Values.messageQueue.type }}
|
value: nats-streaming
|
||||||
{{- if eq .Values.messageQueue.type "nats-streaming" }}
|
|
||||||
- name: MESSAGE_QUEUE_URL
|
- name: MESSAGE_QUEUE_URL
|
||||||
value: nats://{{ .Values.nats.authToken }}@nats-streaming:4222
|
value: nats://{{ .Values.nats.authToken }}@nats-streaming:4222
|
||||||
{{- else if eq .Values.messageQueue.type "azure-storage-queue" }}
|
serviceAccount: fission-svc
|
||||||
|
{{- end }}
|
||||||
|
---
|
||||||
|
{{- if .Values.kafka.enabled }}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mqtrigger-kafka
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: mqtrigger
|
||||||
|
messagequeue: kafka
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mqtrigger
|
||||||
|
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
|
||||||
|
imagePullPolicy: {{ .Values.pullPolicy }}
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--mqt"]
|
||||||
|
env:
|
||||||
|
- name: MESSAGE_QUEUE_TYPE
|
||||||
|
value: kafka
|
||||||
|
- name: MESSAGE_QUEUE_URL
|
||||||
|
value: "{{.Values.kafka.brokers}}"
|
||||||
|
serviceAccount: fission-svc
|
||||||
|
{{- end }}
|
||||||
|
---
|
||||||
|
{{- if .Values.azureStorageQueue.enabled }}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mqtrigger-azure-storage-queue
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: mqtrigger
|
||||||
|
messagequeue: azure-storage-queue
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mqtrigger
|
||||||
|
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
|
||||||
|
imagePullPolicy: {{ .Values.pullPolicy }}
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--mqt", "--routerUrl", "http://router.{{ .Release.Namespace }}"]
|
||||||
|
env:
|
||||||
|
- name: MESSAGE_QUEUE_TYPE
|
||||||
|
value: azure-storage-queue
|
||||||
- name: AZURE_STORAGE_ACCOUNT_NAME
|
- name: AZURE_STORAGE_ACCOUNT_NAME
|
||||||
value: {{ required "An Azure storage account name is required." .Values.azureStorageQueue.accountName }}
|
value: {{ required "An Azure storage account name is required." .Values.azureStorageQueue.accountName }}
|
||||||
- name: AZURE_STORAGE_ACCOUNT_KEY
|
- name: AZURE_STORAGE_ACCOUNT_KEY
|
||||||
@@ -593,9 +647,8 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: azure-storage-account-key
|
name: azure-storage-account-key
|
||||||
key: key
|
key: key
|
||||||
{{- end }}
|
|
||||||
serviceAccount: fission-svc
|
serviceAccount: fission-svc
|
||||||
|
{{- end }}
|
||||||
---
|
---
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ data:
|
|||||||
password: {{ randAlphaNum 20 | b64enc | quote }}
|
password: {{ randAlphaNum 20 | b64enc | quote }}
|
||||||
|
|
||||||
---
|
---
|
||||||
{{- if eq .Values.messageQueue.type "azure-storage-queue" }}
|
{{- if .Values.azureStorageQueue.enabled }}
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ spec:
|
|||||||
svc: controller
|
svc: controller
|
||||||
|
|
||||||
---
|
---
|
||||||
{{- if eq .Values.messageQueue.type "nats-streaming" }}
|
{{- if .Values.nats.enabled }}
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
@@ -57,7 +57,6 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
svc: nats-streaming
|
svc: nats-streaming
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
@@ -53,22 +53,24 @@ logger:
|
|||||||
fluentdImage: fission/fluentd
|
fluentdImage: fission/fluentd
|
||||||
fluentdImageTag: 0.10.0
|
fluentdImageTag: 0.10.0
|
||||||
|
|
||||||
## Type of Queue you would like to use
|
|
||||||
## currently supports nats-streaming, azure-storage-queue
|
|
||||||
messageQueue:
|
|
||||||
type: nats-streaming
|
|
||||||
|
|
||||||
## Message queue trigger config
|
## Message queue trigger config
|
||||||
### NATS Streaming
|
### NATS Streaming, enabled by default
|
||||||
nats:
|
nats:
|
||||||
|
enabled: true
|
||||||
authToken: "defaultFissionAuthToken"
|
authToken: "defaultFissionAuthToken"
|
||||||
clusterID: "fissionMQTrigger"
|
clusterID: "fissionMQTrigger"
|
||||||
|
|
||||||
## Required if messageQueue type is azure-storage-queue
|
## Azure-storage-queue: enable and configure the details
|
||||||
azureStorageQueue:
|
azureStorageQueue:
|
||||||
|
enabled: false
|
||||||
key: ""
|
key: ""
|
||||||
accountName: ""
|
accountName: ""
|
||||||
|
|
||||||
|
## Kafka: enable and configure the details
|
||||||
|
kafka:
|
||||||
|
enabled: false
|
||||||
|
brokers: 'broker.kafka:9092'
|
||||||
|
|
||||||
## Persist data to a persistent volume.
|
## Persist data to a persistent volume.
|
||||||
persistence:
|
persistence:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -53,14 +53,18 @@ func mqtCreate(c *cli.Context) error {
|
|||||||
mqType = fission.MessageQueueTypeNats
|
mqType = fission.MessageQueueTypeNats
|
||||||
case fission.MessageQueueTypeASQ:
|
case fission.MessageQueueTypeASQ:
|
||||||
mqType = fission.MessageQueueTypeASQ
|
mqType = fission.MessageQueueTypeASQ
|
||||||
|
case fission.MessageQueueTypeKafka:
|
||||||
|
mqType = fission.MessageQueueTypeKafka
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatal("Unknown message queue type, currently only \"nats-streaming, azure-storage-queue \" is supported")
|
log.Fatal("Unknown message queue type, currently only \"nats-streaming, azure-storage-queue, kafka \" is supported")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check topic availability
|
// TODO: check topic availability
|
||||||
topic := c.String("topic")
|
topic := c.String("topic")
|
||||||
if len(topic) == 0 {
|
if len(topic) == 0 {
|
||||||
log.Fatal("Listen topic cannot be empty")
|
log.Fatal("Topic cannot be empty")
|
||||||
}
|
}
|
||||||
respTopic := c.String("resptopic")
|
respTopic := c.String("resptopic")
|
||||||
|
|
||||||
|
|||||||
Generated
+19
-5
@@ -1,5 +1,5 @@
|
|||||||
hash: b26f975bf145378e939db833fa4b6a142fa04b884463d8173622b30f95191d42
|
hash: 07bc3f35b3b63ff6f15edf3b8724eb61bd6427572322fc171efbe18cec91cd9e
|
||||||
updated: 2018-08-13T13:12:57.58715-07:00
|
updated: 2018-09-17T14:46:13.995469387+05:30
|
||||||
imports:
|
imports:
|
||||||
- name: cloud.google.com/go
|
- name: cloud.google.com/go
|
||||||
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
|
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
|
||||||
@@ -21,12 +21,14 @@ imports:
|
|||||||
version: 3ac7bf7a47d159a033b107610db8a1b6575507a4
|
version: 3ac7bf7a47d159a033b107610db8a1b6575507a4
|
||||||
subpackages:
|
subpackages:
|
||||||
- quantile
|
- quantile
|
||||||
|
- name: github.com/bsm/sarama-cluster
|
||||||
|
version: c618e605e15c0d7535f6c96ff8efbb0dba4fd66c
|
||||||
- name: github.com/coreos/etcd
|
- name: github.com/coreos/etcd
|
||||||
version: f87b566248bb0713a56dc55bc545aa5aad17ace0
|
version: f87b566248bb0713a56dc55bc545aa5aad17ace0
|
||||||
subpackages:
|
subpackages:
|
||||||
- client
|
- client
|
||||||
- name: github.com/davecgh/go-spew
|
- name: github.com/davecgh/go-spew
|
||||||
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73
|
||||||
subpackages:
|
subpackages:
|
||||||
- spew
|
- spew
|
||||||
- name: github.com/dchest/uniuri
|
- name: github.com/dchest/uniuri
|
||||||
@@ -49,6 +51,14 @@ imports:
|
|||||||
- internal/prefix
|
- internal/prefix
|
||||||
- name: github.com/dustin/go-humanize
|
- name: github.com/dustin/go-humanize
|
||||||
version: 9f541cc9db5d55bce703bd99987c9d5cb8eea45e
|
version: 9f541cc9db5d55bce703bd99987c9d5cb8eea45e
|
||||||
|
- name: github.com/eapache/go-resiliency
|
||||||
|
version: ea41b0fad31007accc7f806884dcdf3da98b79ce
|
||||||
|
subpackages:
|
||||||
|
- breaker
|
||||||
|
- name: github.com/eapache/go-xerial-snappy
|
||||||
|
version: 040cc1a32f578808623071247fdbd5cc43f37f5f
|
||||||
|
- name: github.com/eapache/queue
|
||||||
|
version: 093482f3f8ce946c05bcba64badd2c82369e084d
|
||||||
- name: github.com/fsnotify/fsnotify
|
- name: github.com/fsnotify/fsnotify
|
||||||
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
|
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
|
||||||
- name: github.com/ghodss/yaml
|
- name: github.com/ghodss/yaml
|
||||||
@@ -137,7 +147,7 @@ imports:
|
|||||||
- name: github.com/modern-go/reflect2
|
- name: github.com/modern-go/reflect2
|
||||||
version: 05fbef0ca5da472bbf96c9322b84a53edc03c9fd
|
version: 05fbef0ca5da472bbf96c9322b84a53edc03c9fd
|
||||||
- name: github.com/nats-io/go-nats
|
- name: github.com/nats-io/go-nats
|
||||||
version: 2485387d6ede89c1c8c3445cd1935fd41a2e9ee9
|
version: fb0396ee0bdb8018b0fef30d6d1de798ce99cd05
|
||||||
subpackages:
|
subpackages:
|
||||||
- encoders/builtin
|
- encoders/builtin
|
||||||
- util
|
- util
|
||||||
@@ -146,7 +156,7 @@ imports:
|
|||||||
subpackages:
|
subpackages:
|
||||||
- pb
|
- pb
|
||||||
- name: github.com/nats-io/nats-streaming-server
|
- name: github.com/nats-io/nats-streaming-server
|
||||||
version: 63e2c334b66dba3edade0047625a25c0d8f18f80
|
version: 8910c0c347bc51cc87227aeb27bef19d409bf5c2
|
||||||
subpackages:
|
subpackages:
|
||||||
- spb
|
- spb
|
||||||
- util
|
- util
|
||||||
@@ -179,10 +189,14 @@ imports:
|
|||||||
version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259
|
version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259
|
||||||
subpackages:
|
subpackages:
|
||||||
- xfs
|
- xfs
|
||||||
|
- name: github.com/rcrowley/go-metrics
|
||||||
|
version: e2704e165165ec55d062f5919b4b29494e9fa790
|
||||||
- name: github.com/robfig/cron
|
- name: github.com/robfig/cron
|
||||||
version: b41be1df696709bb6395fe435af20370037c0b4c
|
version: b41be1df696709bb6395fe435af20370037c0b4c
|
||||||
- name: github.com/satori/go.uuid
|
- name: github.com/satori/go.uuid
|
||||||
version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3
|
version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3
|
||||||
|
- name: github.com/Shopify/sarama
|
||||||
|
version: a6144ae922fd99dd0ea5046c8137acfb7fab0914
|
||||||
- name: github.com/sirupsen/logrus
|
- name: github.com/sirupsen/logrus
|
||||||
version: 68cec9f21fbf3ea8d8f98c044bc6ce05f17b267a
|
version: 68cec9f21fbf3ea8d8f98c044bc6ce05f17b267a
|
||||||
- name: github.com/spf13/pflag
|
- name: github.com/spf13/pflag
|
||||||
|
|||||||
@@ -70,3 +70,7 @@ import:
|
|||||||
- package: github.com/dustin/go-humanize
|
- package: github.com/dustin/go-humanize
|
||||||
- package: github.com/golang/protobuf/proto
|
- package: github.com/golang/protobuf/proto
|
||||||
version: v1.1.0
|
version: v1.1.0
|
||||||
|
- package: github.com/bsm/sarama-cluster
|
||||||
|
version: ^2.1.11
|
||||||
|
- package: github.com/Shopify/sarama
|
||||||
|
version: ^1.15.0
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Fission Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package messageQueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
sarama "github.com/Shopify/sarama"
|
||||||
|
cluster "github.com/bsm/sarama-cluster"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
|
"github.com/fission/fission/crd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Kafka struct {
|
||||||
|
routerUrl string
|
||||||
|
brokers []string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeKafkaMessageQueue(routerUrl string, mqCfg MessageQueueConfig) (MessageQueue, error) {
|
||||||
|
if len(routerUrl) == 0 || len(mqCfg.Url) == 0 {
|
||||||
|
return nil, errors.New("The router URL or MQ URL is empty")
|
||||||
|
}
|
||||||
|
kafka := Kafka{
|
||||||
|
routerUrl: routerUrl,
|
||||||
|
brokers: strings.Split(mqCfg.Url, ","),
|
||||||
|
}
|
||||||
|
log.Infof("Created Queue ", kafka)
|
||||||
|
return kafka, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTopicValidForKafka(topic string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kafka Kafka) subscribe(trigger *crd.MessageQueueTrigger) (messageQueueSubscription, error) {
|
||||||
|
log.Infof("Inside kakfa subscribe", trigger)
|
||||||
|
log.Infof("brokers set to ", kafka.brokers)
|
||||||
|
|
||||||
|
// Create new consumer
|
||||||
|
consumerConfig := cluster.NewConfig()
|
||||||
|
consumerConfig.Consumer.Return.Errors = true
|
||||||
|
consumerConfig.Group.Return.Notifications = true
|
||||||
|
consumer, err := cluster.NewConsumer(kafka.brokers, string(trigger.Metadata.UID), []string{trigger.Spec.Topic}, consumerConfig)
|
||||||
|
log.Infof("Created a new consumer ", consumer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new producer
|
||||||
|
producerConfig := sarama.NewConfig()
|
||||||
|
producerConfig.Producer.RequiredAcks = sarama.WaitForAll
|
||||||
|
producerConfig.Producer.Retry.Max = 10
|
||||||
|
producerConfig.Producer.Return.Successes = true
|
||||||
|
producer, err := sarama.NewSyncProducer(kafka.brokers, producerConfig)
|
||||||
|
log.Infof("Created a new producer ", producer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume errors
|
||||||
|
go func() {
|
||||||
|
for err := range consumer.Errors() {
|
||||||
|
log.Printf("Error: %s\n", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// consume notifications
|
||||||
|
go func() {
|
||||||
|
for ntf := range consumer.Notifications() {
|
||||||
|
log.Printf("Rebalanced: %+v\n", ntf)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// consume messages
|
||||||
|
go func() {
|
||||||
|
for msg := range consumer.Messages() {
|
||||||
|
log.Infof("Calling message handler with value " + string(msg.Value[:]))
|
||||||
|
if kafkaMsgHandler(&kafka, producer, trigger, string(msg.Value[:])) {
|
||||||
|
consumer.MarkOffset(msg, "") // mark message as processed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return consumer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kafka Kafka) unsubscribe(subscription messageQueueSubscription) error {
|
||||||
|
return subscription.(*cluster.Consumer).Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func kafkaMsgHandler(kafka *Kafka, producer sarama.SyncProducer, trigger *crd.MessageQueueTrigger, value string) bool {
|
||||||
|
// Support other function ref types
|
||||||
|
if trigger.Spec.FunctionReference.Type != fission.FunctionReferenceTypeFunctionName {
|
||||||
|
log.Fatalf("Unsupported function reference type (%v) for trigger %v",
|
||||||
|
trigger.Spec.FunctionReference.Type, trigger.Metadata.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := kafka.routerUrl + "/" + strings.TrimPrefix(fission.UrlForFunction(trigger.Spec.FunctionReference.Name, trigger.Metadata.Namespace), "/")
|
||||||
|
log.Printf("Making HTTP request to %v", url)
|
||||||
|
headers := map[string]string{
|
||||||
|
"X-Fission-MQTrigger-Topic": trigger.Spec.Topic,
|
||||||
|
"X-Fission-MQTrigger-RespTopic": trigger.Spec.ResponseTopic,
|
||||||
|
"X-Fission-MQTrigger-ErrorTopic": trigger.Spec.ErrorTopic,
|
||||||
|
"Content-Type": trigger.Spec.ContentType,
|
||||||
|
}
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("POST", url, strings.NewReader(value))
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("Request creation failed: %v", url)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
// Make the request
|
||||||
|
var resp *http.Response
|
||||||
|
for attempt := 0; attempt <= trigger.Spec.MaxRetries; attempt++ {
|
||||||
|
// Make the request
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error invoking function for trigger %v: %v", trigger.Metadata.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil && resp.StatusCode == http.StatusOK {
|
||||||
|
// Success, quit retrying
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
log.Warning("Every retry failed; final retry gave empty response.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
log.Infof("Got response " + string(body))
|
||||||
|
if err != nil {
|
||||||
|
errorHandler(trigger, producer, fmt.Sprintf("Request body error: %v", string(body)))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
errorHandler(trigger, producer, fmt.Sprintf("Request returned failure: %v", resp.StatusCode))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(trigger.Spec.ResponseTopic) > 0 {
|
||||||
|
_, _, err := producer.SendMessage(&sarama.ProducerMessage{
|
||||||
|
Topic: trigger.Spec.ResponseTopic,
|
||||||
|
Value: sarama.StringEncoder(body),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("Failed to publish message to topic %s: %v", trigger.Spec.ResponseTopic, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorHandler(trigger *crd.MessageQueueTrigger, producer sarama.SyncProducer, body string) {
|
||||||
|
if len(trigger.Spec.ErrorTopic) > 0 {
|
||||||
|
_, _, err := producer.SendMessage(&sarama.ProducerMessage{
|
||||||
|
Topic: trigger.Spec.ErrorTopic,
|
||||||
|
Value: sarama.StringEncoder(body),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("Failed to publish message to error topic %s: %v", trigger.Spec.ErrorTopic, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Fission Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
package messageQueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
cluster "github.com/bsm/sarama-cluster"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
|
"github.com/fission/fission/crd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DummyKafkaBrokers = "http://kafka-0.kafka:9092,http://kafka-1.kafka:9092"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kafkaClusterMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type Consumer struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *kafkaClusterMock) NewConsumer(addrs []string, groupID string, topics []string, config *cluster.Config) (*Consumer, error) {
|
||||||
|
args := m.Called(addrs, groupID, topics, config)
|
||||||
|
res := args.Get(0).(*Consumer)
|
||||||
|
//err := args.Error(1)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKafkaMQConfigValid(t *testing.T) {
|
||||||
|
kafkaConfig, err := makeKafkaMessageQueue(DummyRouterURL, MessageQueueConfig{
|
||||||
|
MQType: fission.MessageQueueTypeKafka,
|
||||||
|
Url: DummyKafkaBrokers,
|
||||||
|
})
|
||||||
|
require.NotNil(t, kafkaConfig)
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKafkaMQConfigMissingBroker(t *testing.T) {
|
||||||
|
kafkaConfig, err := makeKafkaMessageQueue(DummyRouterURL, MessageQueueConfig{
|
||||||
|
MQType: fission.MessageQueueTypeKafka,
|
||||||
|
Url: "",
|
||||||
|
})
|
||||||
|
require.Nil(t, kafkaConfig)
|
||||||
|
require.Error(t, err, "The router URL or MQ URL is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKafkaMQConfigMissingRouter(t *testing.T) {
|
||||||
|
kafkaConfig, err := makeKafkaMessageQueue("", MessageQueueConfig{
|
||||||
|
MQType: fission.MessageQueueTypeKafka,
|
||||||
|
Url: DummyKafkaBrokers,
|
||||||
|
})
|
||||||
|
require.Nil(t, kafkaConfig)
|
||||||
|
require.Error(t, err, "The router URL or MQ URL is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKafkaMq(t *testing.T) {
|
||||||
|
// This is a WIP test and does not yet work correctly, hence skipping for now
|
||||||
|
t.SkipNow()
|
||||||
|
|
||||||
|
const (
|
||||||
|
TriggerName = "queuetrigger"
|
||||||
|
QueueName = "inputqueue"
|
||||||
|
MessageBody = "input"
|
||||||
|
FunctionName = "testfunc"
|
||||||
|
ContentType = "text/plain"
|
||||||
|
)
|
||||||
|
|
||||||
|
kafkaConfig, err := makeKafkaMessageQueue(DummyRouterURL, MessageQueueConfig{
|
||||||
|
MQType: fission.MessageQueueTypeKafka,
|
||||||
|
Url: DummyKafkaBrokers,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
consumer := new(kafkaClusterMock)
|
||||||
|
consumer.On(
|
||||||
|
"NewConsumer",
|
||||||
|
mock.AnythingOfType("test"),
|
||||||
|
).Return(
|
||||||
|
&Consumer{},
|
||||||
|
nil,
|
||||||
|
).Once()
|
||||||
|
|
||||||
|
kafkaConfig.subscribe(&crd.MessageQueueTrigger{
|
||||||
|
Metadata: metav1.ObjectMeta{
|
||||||
|
Name: TriggerName,
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: fission.MessageQueueTriggerSpec{
|
||||||
|
FunctionReference: fission.FunctionReference{
|
||||||
|
Type: fission.FunctionReferenceTypeFunctionName,
|
||||||
|
Name: FunctionName,
|
||||||
|
},
|
||||||
|
MessageQueueType: fission.MessageQueueTypeASQ,
|
||||||
|
Topic: QueueName,
|
||||||
|
ContentType: ContentType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
"github.com/fission/fission/crd"
|
"github.com/fission/fission/crd"
|
||||||
|
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -86,6 +87,8 @@ func MakeMessageQueueTriggerManager(fissionClient *crd.FissionClient, routerUrl
|
|||||||
messageQueue, err = makeNatsMessageQueue(routerUrl, mqConfig)
|
messageQueue, err = makeNatsMessageQueue(routerUrl, mqConfig)
|
||||||
case fission.MessageQueueTypeASQ:
|
case fission.MessageQueueTypeASQ:
|
||||||
messageQueue, err = newAzureStorageConnection(routerUrl, mqConfig)
|
messageQueue, err = newAzureStorageConnection(routerUrl, mqConfig)
|
||||||
|
case fission.MessageQueueTypeKafka:
|
||||||
|
messageQueue, err = makeKafkaMessageQueue(routerUrl, mqConfig)
|
||||||
default:
|
default:
|
||||||
err = errors.New("No matched message queue type found")
|
err = errors.New("No matched message queue type found")
|
||||||
}
|
}
|
||||||
@@ -221,3 +224,13 @@ func (mqt *MessageQueueTriggerManager) syncTriggers() {
|
|||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsTopicValid(mqType string, topic string) bool {
|
||||||
|
switch mqType {
|
||||||
|
case fv1.MessageQueueTypeNats:
|
||||||
|
return isTopicValidForNats(topic)
|
||||||
|
case fv1.MessageQueueTypeKafka:
|
||||||
|
return isTopicValidForKafka(topic)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,8 +64,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageQueueTypeNats = "nats-streaming"
|
MessageQueueTypeNats = "nats-streaming"
|
||||||
MessageQueueTypeASQ = "azure-storage-queue"
|
MessageQueueTypeASQ = "azure-storage-queue"
|
||||||
|
MessageQueueTypeKafka = "kafka"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
validAzureQueueName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]*[a-z0-9]$")
|
validAzureQueueName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-]*[a-z0-9]$")
|
||||||
|
validKafkaTopicName = regexp.MustCompile("^[a-z0-9][a-z0-9\\-._]*[a-z0-9]$")
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -164,10 +165,29 @@ func IsTopicValid(mqType MessageQueueType, topic string) bool {
|
|||||||
return nsUtil.IsChannelNameValid(topic, false)
|
return nsUtil.IsChannelNameValid(topic, false)
|
||||||
case MessageQueueTypeASQ:
|
case MessageQueueTypeASQ:
|
||||||
return len(topic) >= 3 && len(topic) <= 63 && validAzureQueueName.MatchString(topic)
|
return len(topic) >= 3 && len(topic) <= 63 && validAzureQueueName.MatchString(topic)
|
||||||
|
case MessageQueueTypeKafka:
|
||||||
|
return IsValidKafkaTopic(topic)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The validation is based on Kafka's internal implementation: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/internals/Topic.java
|
||||||
|
func IsValidKafkaTopic(topic string) bool {
|
||||||
|
if len(topic) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if topic == "." || topic == ".." {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(topic) > 249 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !validKafkaTopicName.MatchString(topic) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func IsValidCronSpec(spec string) error {
|
func IsValidCronSpec(spec string) error {
|
||||||
_, err := cron.Parse(spec)
|
_, err := cron.Parse(spec)
|
||||||
return err
|
return err
|
||||||
@@ -433,7 +453,7 @@ func (spec MessageQueueTriggerSpec) Validate() error {
|
|||||||
result = multierror.Append(result, spec.FunctionReference.Validate())
|
result = multierror.Append(result, spec.FunctionReference.Validate())
|
||||||
|
|
||||||
switch spec.MessageQueueType {
|
switch spec.MessageQueueType {
|
||||||
case MessageQueueTypeNats, MessageQueueTypeASQ: // no op
|
case MessageQueueTypeNats, MessageQueueTypeASQ, MessageQueueTypeKafka: // no op
|
||||||
default:
|
default:
|
||||||
result = multierror.Append(result, MakeValidationErr(ErrorUnsupportedType, "MessageQueueTriggerSpec.MessageQueueType", spec.MessageQueueType, "not a supported message queue type"))
|
result = multierror.Append(result, MakeValidationErr(ErrorUnsupportedType, "MessageQueueTriggerSpec.MessageQueueType", spec.MessageQueueType, "not a supported message queue type"))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -435,7 +435,7 @@ dump_logs() {
|
|||||||
dump_fission_logs $ns $fns executor
|
dump_fission_logs $ns $fns executor
|
||||||
dump_fission_logs $ns $fns storagesvc
|
dump_fission_logs $ns $fns storagesvc
|
||||||
dump_fission_logs $ns $fns mqtrigger
|
dump_fission_logs $ns $fns mqtrigger
|
||||||
dump_fission_logs $ns $fns nats-streaming
|
dump_fission_logs $ns $fns mqtrigger-nats-streaming
|
||||||
dump_function_pod_logs $ns $fns
|
dump_function_pod_logs $ns $fns
|
||||||
dump_builder_pod_logs $bns
|
dump_builder_pod_logs $bns
|
||||||
dump_fission_crds
|
dump_fission_crds
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ waitBuild() {
|
|||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
log "Waiting for build to finish"
|
||||||
|
sleep 1
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
export -f waitBuild
|
export -f waitBuild
|
||||||
|
|||||||
@@ -140,8 +140,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageQueueTypeNats = fv1.MessageQueueTypeNats
|
MessageQueueTypeNats = fv1.MessageQueueTypeNats
|
||||||
MessageQueueTypeASQ = fv1.MessageQueueTypeASQ
|
MessageQueueTypeASQ = fv1.MessageQueueTypeASQ
|
||||||
|
MessageQueueTypeKafka = fv1.MessageQueueTypeKafka
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
Reference in New Issue
Block a user