├── frontend
├── .gitignore
├── src
│ └── main
│ │ ├── fabric8
│ │ └── deployment.yaml
│ │ ├── java
│ │ └── io
│ │ │ └── openshift
│ │ │ └── booster
│ │ │ └── messaging
│ │ │ ├── Request.java
│ │ │ ├── Response.java
│ │ │ ├── Data.java
│ │ │ ├── WorkerUpdate.java
│ │ │ └── Frontend.java
│ │ └── resources
│ │ └── webroot
│ │ ├── index.html
│ │ ├── app.js
│ │ ├── app.css
│ │ └── gesso.js
├── pom.xml
└── .openshiftio
│ ├── application.yaml
│ └── service.yaml
├── images
└── CopyLogin.png
├── worker
├── .gitignore
├── integration-tests
│ ├── src
│ │ └── test
│ │ │ ├── resources
│ │ │ ├── arquillian.xml
│ │ │ └── amq.yaml
│ │ │ └── java
│ │ │ └── io
│ │ │ └── openshift
│ │ │ └── booster
│ │ │ └── OpenShiftIT.java
│ └── pom.xml
├── src
│ └── main
│ │ ├── fabric8
│ │ └── deployment.yaml
│ │ └── java
│ │ └── io
│ │ └── openshift
│ │ └── booster
│ │ └── messaging
│ │ └── Worker.java
├── pom.xml
├── .openshiftio
│ └── application.yaml
├── service.yaml
└── LICENSE
├── scripts
├── exportEnvVars.sh
├── setup-gcp-all
├── setup-azure-all
├── setup-aws-ic-only
├── setup-gcp-ic-only
├── setup-azure-ic-only
├── setup-onprem-ic-only
├── workload.yaml
├── setup-aws-all
├── setup-onprem-all
├── qdrouterd.conf.public_cloud
├── ic.aws.yaml
├── ic.gcp.yaml
├── ic.azure.yaml
├── ic.onprem.yaml
├── generate-certs
└── qdrouterd.conf.onprem
├── pom.xml
├── controller
├── package.json
├── package-lock.json
└── index.html
├── client
├── Dockerfile
└── client.py
├── server
├── Dockerfile
└── service.py
├── messaging-load
├── Dockerfile
├── load.py
└── local_load.py
├── .gitignore
├── README.md
└── LICENSE
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /.vertx
3 |
--------------------------------------------------------------------------------
/images/CopyLogin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burrsutter/demo2-amq/HEAD/images/CopyLogin.png
--------------------------------------------------------------------------------
/worker/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /.vertx
3 | /bin
4 | .settings
5 | .project
6 | .classpath
7 | .DS_Store
--------------------------------------------------------------------------------
/scripts/exportEnvVars.sh:
--------------------------------------------------------------------------------
1 | export ONPREM_SUFFIX=apps.gcp.burrsutter.dev
2 | export AWS_SUFFIX=apps.aws.burrsutter.org
3 | #export GCP_SUFFIX=apps.gcp.burrsutter.dev
4 | export AZURE_SUFFIX=apps.azr.burrsutter.net
5 | echo $ONPREM_SUFFIX
6 | echo $AWS_SUFFIX
7 | # echo $GCP_SUFFIX
8 | echo $AZURE_SUFFIX
9 |
--------------------------------------------------------------------------------
/scripts/setup-gcp-all:
--------------------------------------------------------------------------------
1 | ./setup-gcp-ic-only
2 | oc apply -f workload.yaml
3 | oc apply -f ../worker/.openshiftio/application.yaml
4 | oc new-app --template=vertx-messaging-worker \
5 | -p SOURCE_REPOSITORY_URL=https://github.com/burrsutter/demo2-amq \
6 | -p SOURCE_REPOSITORY_REF=master \
7 | -p SOURCE_REPOSITORY_DIR=worker
8 |
9 |
--------------------------------------------------------------------------------
/scripts/setup-azure-all:
--------------------------------------------------------------------------------
1 | ./setup-azure-ic-only
2 | oc apply -f workload.yaml
3 | oc apply -f ../worker/.openshiftio/application.yaml
4 | oc new-app --template=vertx-messaging-worker \
5 | -p SOURCE_REPOSITORY_URL=https://github.com/burrsutter/demo2-amq \
6 | -p SOURCE_REPOSITORY_REF=master \
7 | -p SOURCE_REPOSITORY_DIR=worker
8 |
9 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.example
5 | mylib
6 | 1.0
7 |
8 | pom
9 |
10 |
11 | frontend
12 | worker
13 |
14 |
15 |
--------------------------------------------------------------------------------
/controller/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "controller",
3 | "version": "1.0.0",
4 | "description": "Controller for the AMQ workload in Demo-2",
5 | "main": "index.html",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "Apache-2.0",
11 | "dependencies": {
12 | "rhea": "^0.2.11"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/setup-aws-ic-only:
--------------------------------------------------------------------------------
1 | oc new-project demo2-amq
2 | oc create secret generic qdr-internal-cert --from-file=tls.crt=tls-certs/tls.aws.crt --from-file=tls.key=tls-certs/tls.aws.key --from-file=ca.crt=tls-certs/ca.crt --from-file=tls.pw=tls-certs/tls.aws.pw
3 | oc create configmap qdr-config --from-file=qdrouterd.conf.template=qdrouterd.conf.public_cloud --dry-run -o yaml | oc apply -f -
4 | cat ic.aws.yaml | envsubst | oc apply -f -
5 |
--------------------------------------------------------------------------------
/scripts/setup-gcp-ic-only:
--------------------------------------------------------------------------------
1 | oc new-project demo2-amq
2 | oc create secret generic qdr-internal-cert --from-file=tls.crt=tls-certs/tls.gcp.crt --from-file=tls.key=tls-certs/tls.gcp.key --from-file=ca.crt=tls-certs/ca.crt --from-file=tls.pw=tls-certs/tls.gcp.pw
3 | oc create configmap qdr-config --from-file=qdrouterd.conf.template=qdrouterd.conf.public_cloud --dry-run -o yaml | oc apply -f -
4 | cat ic.gcp.yaml | envsubst | oc apply -f -
5 |
--------------------------------------------------------------------------------
/scripts/setup-azure-ic-only:
--------------------------------------------------------------------------------
1 | oc new-project demo2-amq
2 | oc create secret generic qdr-internal-cert --from-file=tls.crt=tls-certs/tls.azure.crt --from-file=tls.key=tls-certs/tls.azure.key --from-file=ca.crt=tls-certs/ca.crt --from-file=tls.pw=tls-certs/tls.azure.pw
3 | oc create configmap qdr-config --from-file=qdrouterd.conf.template=qdrouterd.conf.public_cloud --dry-run -o yaml | oc apply -f -
4 | cat ic.azure.yaml | envsubst | oc apply -f -
5 |
--------------------------------------------------------------------------------
/scripts/setup-onprem-ic-only:
--------------------------------------------------------------------------------
1 | oc new-project demo2-amq
2 | oc create secret generic qdr-internal-cert --from-file=tls.crt=tls-certs/tls.onprem.crt --from-file=tls.key=tls-certs/tls.onprem.key --from-file=ca.crt=tls-certs/ca.crt --from-file=tls.pw=tls-certs/tls.onprem.pw
3 | oc create configmap qdr-config --from-file=qdrouterd.conf.template=qdrouterd.conf.onprem --dry-run -o yaml | oc apply -f -
4 | cat ic.onprem.yaml | envsubst | oc apply -f -
5 |
--------------------------------------------------------------------------------
/scripts/workload.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: workload-generator
5 | spec:
6 | replicas: 0
7 | template:
8 | metadata:
9 | labels:
10 | app: workload-generator
11 | version: v1
12 | type: server
13 | spec:
14 | containers:
15 | - name: client-load
16 | image: tedross/demo2-messaging-load:1.0
17 | imagePullPolicy: IfNotPresent
18 |
--------------------------------------------------------------------------------
/worker/integration-tests/src/test/resources/arquillian.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | true
7 | true
8 | false
9 |
10 |
11 |
--------------------------------------------------------------------------------
/scripts/setup-aws-all:
--------------------------------------------------------------------------------
1 | ./setup-aws-ic-only
2 | oc apply -f workload.yaml
3 | oc apply -f ../frontend/.openshiftio/application.yaml
4 | oc new-app --template=vertx-messaging-frontend \
5 | -p SOURCE_REPOSITORY_URL=https://github.com/burrsutter/demo2-amq \
6 | -p SOURCE_REPOSITORY_REF=master \
7 | -p SOURCE_REPOSITORY_DIR=frontend
8 | oc apply -f ../worker/.openshiftio/application.yaml
9 | oc new-app --template=vertx-messaging-worker \
10 | -p SOURCE_REPOSITORY_URL=https://github.com/burrsutter/demo2-amq \
11 | -p SOURCE_REPOSITORY_REF=master \
12 | -p SOURCE_REPOSITORY_DIR=worker
13 |
14 |
--------------------------------------------------------------------------------
/scripts/setup-onprem-all:
--------------------------------------------------------------------------------
1 | ./setup-onprem-ic-only
2 | oc apply -f workload.yaml
3 | oc apply -f ../frontend/.openshiftio/application.yaml
4 | # oc new-app --template=vertx-messaging-frontend \
5 | # -p SOURCE_REPOSITORY_URL=https://github.com/burrsutter/demo2-amq \
6 | # -p SOURCE_REPOSITORY_REF=master \
7 | # -p SOURCE_REPOSITORY_DIR=frontend
8 | oc apply -f ../worker/.openshiftio/application.yaml
9 | oc new-app --template=vertx-messaging-worker \
10 | -p SOURCE_REPOSITORY_URL=https://github.com/burrsutter/demo2-amq \
11 | -p SOURCE_REPOSITORY_REF=master \
12 | -p SOURCE_REPOSITORY_DIR=worker
13 |
14 |
--------------------------------------------------------------------------------
/frontend/src/main/fabric8/deployment.yaml:
--------------------------------------------------------------------------------
1 | spec:
2 | template:
3 | spec:
4 | containers:
5 | - env:
6 | - name: MESSAGING_SERVICE_HOST
7 | value: messaging
8 | - name: MESSAGING_SERVICE_PORT
9 | value: 5672
10 | readinessProbe:
11 | httpGet:
12 | path: /health
13 | port: 8080
14 | scheme: HTTP
15 | initialDelaySeconds: 10
16 | livenessProbe:
17 | httpGet:
18 | path: /health
19 | port: 8080
20 | scheme: HTTP
21 | initialDelaySeconds: 180
22 |
23 |
--------------------------------------------------------------------------------
/worker/src/main/fabric8/deployment.yaml:
--------------------------------------------------------------------------------
1 | spec:
2 | template:
3 | spec:
4 | containers:
5 | - env:
6 | - name: MESSAGING_SERVICE_HOST
7 | value: work-queue-broker-amq-amqp
8 | - name: MESSAGING_SERVICE_PORT
9 | value: 5672
10 | - name: AMQ_LOCATION_KEY
11 | value: AWS
12 | readinessProbe:
13 | httpGet:
14 | path: /
15 | port: 8080
16 | scheme: HTTP
17 | initialDelaySeconds: 10
18 | livenessProbe:
19 | httpGet:
20 | path: /
21 | port: 8080
22 | scheme: HTTP
23 | initialDelaySeconds: 180
24 |
--------------------------------------------------------------------------------
/controller/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "controller",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "debug": {
8 | "version": "3.1.0",
9 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
10 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
11 | "requires": {
12 | "ms": "2.0.0"
13 | }
14 | },
15 | "ms": {
16 | "version": "2.0.0",
17 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
18 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
19 | },
20 | "rhea": {
21 | "version": "0.2.11",
22 | "resolved": "https://registry.npmjs.org/rhea/-/rhea-0.2.11.tgz",
23 | "integrity": "sha1-g/gkgfxGHh44+AvKXu56umQWaek=",
24 | "requires": {
25 | "debug": "3.1.0"
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/client/Dockerfile:
--------------------------------------------------------------------------------
1 | ##
2 | ## Licensed to the Apache Software Foundation (ASF) under one
3 | ## or more contributor license agreements. See the NOTICE file
4 | ## distributed with this work for additional information
5 | ## regarding copyright ownership. The ASF licenses this file
6 | ## to you under the Apache License, Version 2.0 (the
7 | ## "License"); you may not use this file except in compliance
8 | ## with the License. You may obtain a copy of the License at
9 | ##
10 | ## http://www.apache.org/licenses/LICENSE-2.0
11 | ##
12 | ## Unless required by applicable law or agreed to in writing,
13 | ## software distributed under the License is distributed on an
14 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | ## KIND, either express or implied. See the License for the
16 | ## specific language governing permissions and limitations
17 | ## under the License.
18 | ##
19 |
20 | FROM tedross/demo-base
21 | MAINTAINER Ted Ross
22 | ADD client.py /client.py
23 | CMD python /client.py
24 |
25 |
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | ##
2 | ## Licensed to the Apache Software Foundation (ASF) under one
3 | ## or more contributor license agreements. See the NOTICE file
4 | ## distributed with this work for additional information
5 | ## regarding copyright ownership. The ASF licenses this file
6 | ## to you under the Apache License, Version 2.0 (the
7 | ## "License"); you may not use this file except in compliance
8 | ## with the License. You may obtain a copy of the License at
9 | ##
10 | ## http://www.apache.org/licenses/LICENSE-2.0
11 | ##
12 | ## Unless required by applicable law or agreed to in writing,
13 | ## software distributed under the License is distributed on an
14 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | ## KIND, either express or implied. See the License for the
16 | ## specific language governing permissions and limitations
17 | ## under the License.
18 | ##
19 |
20 | FROM tedross/demo-base
21 | MAINTAINER Ted Ross
22 | ADD service.py /service.py
23 | CMD python /service.py
24 |
25 |
--------------------------------------------------------------------------------
/messaging-load/Dockerfile:
--------------------------------------------------------------------------------
1 | ##
2 | ## Licensed to the Apache Software Foundation (ASF) under one
3 | ## or more contributor license agreements. See the NOTICE file
4 | ## distributed with this work for additional information
5 | ## regarding copyright ownership. The ASF licenses this file
6 | ## to you under the Apache License, Version 2.0 (the
7 | ## "License"); you may not use this file except in compliance
8 | ## with the License. You may obtain a copy of the License at
9 | ##
10 | ## http://www.apache.org/licenses/LICENSE-2.0
11 | ##
12 | ## Unless required by applicable law or agreed to in writing,
13 | ## software distributed under the License is distributed on an
14 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | ## KIND, either express or implied. See the License for the
16 | ## specific language governing permissions and limitations
17 | ## under the License.
18 | ##
19 |
20 | FROM tedross/demo-base
21 | MAINTAINER Ted Ross
22 | ADD local_load.py /local_load.py
23 | CMD python /local_load.py
24 |
25 |
--------------------------------------------------------------------------------
/scripts/qdrouterd.conf.public_cloud:
--------------------------------------------------------------------------------
1 | router {
2 | mode: interior
3 | id: ${AMQ_LOCATION_KEY}
4 | }
5 |
6 | listener {
7 | host: 0.0.0.0
8 | port: amqp
9 | authenticatePeer: no
10 | saslMechanisms: ANONYMOUS
11 | }
12 |
13 | listener {
14 | host: 0.0.0.0
15 | port: 55672
16 | role: inter-router
17 | authenticatePeer: yes
18 | sslProfile: ssl_internal_details
19 | saslMechanisms: EXTERNAL
20 | }
21 |
22 | sslProfile {
23 | name: ssl_internal_details
24 | certFile: /etc/qpid-dispatch-certs/internal/tls.crt
25 | privateKeyFile: /etc/qpid-dispatch-certs/internal/tls.key
26 | passwordFile: /etc/qpid-dispatch-certs/internal/tls.pw
27 | caCertFile: /etc/qpid-dispatch-certs/internal/ca.crt
28 | }
29 |
30 | address {
31 | prefix: closest
32 | distribution: closest
33 | }
34 |
35 | address {
36 | prefix: multicast
37 | distribution: multicast
38 | }
39 |
40 | address {
41 | prefix: unicast
42 | distribution: closest
43 | }
44 |
45 | address {
46 | prefix: exclusive
47 | distribution: closest
48 | }
49 |
50 | address {
51 | prefix: broadcast
52 | distribution: multicast
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/main/java/io/openshift/booster/messaging/Request.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Red Hat, Inc, and individual contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.openshift.booster.messaging;
18 |
19 | public class Request {
20 | private String text;
21 | private boolean uppercase;
22 | private boolean reverse;
23 |
24 | public String getText() {
25 | return text;
26 | }
27 |
28 | public void setText(String text) {
29 | this.text = text;
30 | }
31 |
32 | public boolean isUppercase() {
33 | return uppercase;
34 | }
35 |
36 | public boolean isReverse() {
37 | return reverse;
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return String.format("Request{text=%s, uppercase=%s, reverse=%s}",
43 | text, uppercase, reverse);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/main/java/io/openshift/booster/messaging/Response.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Red Hat, Inc, and individual contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.openshift.booster.messaging;
18 |
19 | public class Response {
20 | private final String requestId;
21 | private final String workerId;
22 | private final String cloudId;
23 | private final String text;
24 |
25 | public Response(String requestId, String workerId, String cloudId, String text) {
26 | this.requestId = requestId;
27 | this.workerId = workerId;
28 | this.cloudId = cloudId;
29 | this.text = text;
30 | }
31 |
32 | public String getRequestId() {
33 | return requestId;
34 | }
35 |
36 | public String getWorkerId() {
37 | return workerId;
38 | }
39 |
40 | public String getText() {
41 | return text;
42 | }
43 |
44 | public String getCloudId() {
45 | return cloudId;
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return String.format("Response{requestId=%s, workerId=%s, cloudId=%s, text=%s}",
51 | requestId, workerId, cloudId, text);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/scripts/ic.aws.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: messaging
5 | labels:
6 | app: amq-interconnect
7 | annotations:
8 | service.alpha.openshift.io/serving-cert-secret-name: qdr-external-cert
9 | spec:
10 | ports:
11 | - port: 5672
12 | name: amqp
13 | - port: 55672
14 | name: inter-router
15 | selector:
16 | type: qdrouterd
17 | ---
18 | apiVersion: extensions/v1beta1
19 | kind: Deployment
20 | metadata:
21 | name: amq-interconnect
22 | spec:
23 | replicas: 1
24 | template:
25 | metadata:
26 | labels:
27 | app: amq-interconnect
28 | version: v1
29 | type: qdrouterd
30 | spec:
31 | containers:
32 | - name: amq-interconnect
33 | env:
34 | - name: AMQ_LOCATION_KEY
35 | value: AWS
36 | image: tedross/qpid-dispatch-router:1.2.0
37 | imagePullPolicy: Always
38 | ports:
39 | - containerPort: 5672
40 | protocol: TCP
41 | volumeMounts:
42 | - name: qdr-config
43 | mountPath: /etc/qpid-dispatch
44 | - name: internal-certs
45 | readOnly: true
46 | mountPath: /etc/qpid-dispatch-certs/internal
47 | volumes:
48 | - name: qdr-config
49 | configMap:
50 | name: qdr-config
51 | - name: internal-certs
52 | secret:
53 | secretName: qdr-internal-cert
54 | ---
55 | apiVersion: v1
56 | kind: Route
57 | metadata:
58 | labels:
59 | app: amq-interconnect
60 | name: inter-router
61 | spec:
62 | host: inter-router-demo2-amq.${AWS_SUFFIX}
63 | port:
64 | targetPort: inter-router
65 | tls:
66 | termination: passthrough
67 | to:
68 | kind: Service
69 | name: messaging
70 | weight: 100
71 | wildcardPolicy: None
72 |
--------------------------------------------------------------------------------
/scripts/ic.gcp.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: messaging
5 | labels:
6 | app: amq-interconnect
7 | annotations:
8 | service.alpha.openshift.io/serving-cert-secret-name: qdr-external-cert
9 | spec:
10 | ports:
11 | - port: 5672
12 | name: amqp
13 | - port: 55672
14 | name: inter-router
15 | selector:
16 | type: qdrouterd
17 | ---
18 | apiVersion: extensions/v1beta1
19 | kind: Deployment
20 | metadata:
21 | name: amq-interconnect
22 | spec:
23 | replicas: 1
24 | template:
25 | metadata:
26 | labels:
27 | app: amq-interconnect
28 | version: v1
29 | type: qdrouterd
30 | spec:
31 | containers:
32 | - name: amq-interconnect
33 | env:
34 | - name: AMQ_LOCATION_KEY
35 | value: GCP
36 | image: tedross/qpid-dispatch-router:1.2.0
37 | imagePullPolicy: Always
38 | ports:
39 | - containerPort: 5672
40 | protocol: TCP
41 | volumeMounts:
42 | - name: qdr-config
43 | mountPath: /etc/qpid-dispatch
44 | - name: internal-certs
45 | readOnly: true
46 | mountPath: /etc/qpid-dispatch-certs/internal
47 | volumes:
48 | - name: qdr-config
49 | configMap:
50 | name: qdr-config
51 | - name: internal-certs
52 | secret:
53 | secretName: qdr-internal-cert
54 | ---
55 | apiVersion: v1
56 | kind: Route
57 | metadata:
58 | labels:
59 | app: amq-interconnect
60 | name: inter-router
61 | spec:
62 | host: inter-router-demo2-amq.${GCP_SUFFIX}
63 | port:
64 | targetPort: inter-router
65 | tls:
66 | termination: passthrough
67 | to:
68 | kind: Service
69 | name: messaging
70 | weight: 100
71 | wildcardPolicy: None
72 |
--------------------------------------------------------------------------------
/scripts/ic.azure.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: messaging
5 | labels:
6 | app: amq-interconnect
7 | annotations:
8 | service.alpha.openshift.io/serving-cert-secret-name: qdr-external-cert
9 | spec:
10 | ports:
11 | - port: 5672
12 | name: amqp
13 | - port: 55672
14 | name: inter-router
15 | selector:
16 | type: qdrouterd
17 | ---
18 | apiVersion: extensions/v1beta1
19 | kind: Deployment
20 | metadata:
21 | name: amq-interconnect
22 | spec:
23 | replicas: 1
24 | template:
25 | metadata:
26 | labels:
27 | app: amq-interconnect
28 | version: v1
29 | type: qdrouterd
30 | spec:
31 | containers:
32 | - name: amq-interconnect
33 | env:
34 | - name: AMQ_LOCATION_KEY
35 | value: AZR
36 | image: tedross/qpid-dispatch-router:1.2.0
37 | imagePullPolicy: Always
38 | ports:
39 | - containerPort: 5672
40 | protocol: TCP
41 | volumeMounts:
42 | - name: qdr-config
43 | mountPath: /etc/qpid-dispatch
44 | - name: internal-certs
45 | readOnly: true
46 | mountPath: /etc/qpid-dispatch-certs/internal
47 | volumes:
48 | - name: qdr-config
49 | configMap:
50 | name: qdr-config
51 | - name: internal-certs
52 | secret:
53 | secretName: qdr-internal-cert
54 | ---
55 | apiVersion: v1
56 | kind: Route
57 | metadata:
58 | labels:
59 | app: amq-interconnect
60 | name: inter-router
61 | spec:
62 | host: inter-router-demo2-amq.${AZURE_SUFFIX}
63 | port:
64 | targetPort: inter-router
65 | tls:
66 | termination: passthrough
67 | to:
68 | kind: Service
69 | name: messaging
70 | weight: 100
71 | wildcardPolicy: None
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | controller/node_modules/
104 |
105 | # Generated TLS certificates and keys
106 | scripts/tls-certs
107 |
108 | /target
109 | /.vertx
110 | /bin
111 | .settings
112 | .project
113 | .classpath
114 | .DS_Store
115 | .vscode
116 | /scripts/demo
--------------------------------------------------------------------------------
/frontend/src/main/java/io/openshift/booster/messaging/Data.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package io.openshift.booster.messaging;
19 |
20 | import java.util.Map;
21 | import java.util.Queue;
22 | import java.util.concurrent.ConcurrentHashMap;
23 | import java.util.concurrent.ConcurrentLinkedQueue;
24 |
25 | public class Data {
26 | private final Queue requestIds;
27 | private final Map responses;
28 | private final Map workers;
29 |
30 | public Data() {
31 | this.requestIds = new ConcurrentLinkedQueue<>();
32 | this.responses = new ConcurrentHashMap<>();
33 | this.workers = new ConcurrentHashMap<>();
34 | }
35 |
36 | public Queue getRequestIds() {
37 | return requestIds;
38 | }
39 |
40 | public Map getResponses() {
41 | return responses;
42 | }
43 |
44 | public Map getWorkers() {
45 | return workers;
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return String.format("Data{requestIds=%s, responses=%s, workers=%s}",
51 | requestIds, responses, workers);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/main/java/io/openshift/booster/messaging/WorkerUpdate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Red Hat, Inc, and individual contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.openshift.booster.messaging;
18 |
19 | public class WorkerUpdate {
20 | private final String workerId;
21 | private final String cloud;
22 | private final long timestamp;
23 | private final long requestsProcessed;
24 | private final long processingErrors;
25 |
26 | public WorkerUpdate(String workerId, String cloud, long timestamp, long requestsProcessed,
27 | long processingErrors) {
28 | this.workerId = workerId;
29 | this.cloud = cloud;
30 | this.timestamp = timestamp;
31 | this.requestsProcessed = requestsProcessed;
32 | this.processingErrors = processingErrors;
33 | }
34 |
35 | public String getWorkerId() {
36 | return workerId;
37 | }
38 |
39 | public String getCloud() {
40 | return cloud;
41 | }
42 |
43 | public long getTimestamp() {
44 | return timestamp;
45 | }
46 |
47 | public long getRequestsProcessed() {
48 | return requestsProcessed;
49 | }
50 |
51 | public long getProcessingErrors() {
52 | return processingErrors;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return String.format("WorkerUpdate{workerId=%s, cloud=%s, timestamp=%s, requestsProcessed=%s, processingErrors=%s}",
58 | workerId, cloud, timestamp, requestsProcessed, processingErrors);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/scripts/ic.onprem.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: messaging
5 | labels:
6 | app: amq-interconnect
7 | annotations:
8 | service.alpha.openshift.io/serving-cert-secret-name: qdr-external-cert
9 | spec:
10 | ports:
11 | - port: 5671
12 | name: amqps
13 | - port: 5672
14 | name: amqp
15 | - port: 8672
16 | name: console
17 | selector:
18 | type: qdrouterd
19 | ---
20 | apiVersion: extensions/v1beta1
21 | kind: Deployment
22 | metadata:
23 | name: amq-interconnect
24 | spec:
25 | replicas: 1
26 | template:
27 | metadata:
28 | labels:
29 | app: amq-interconnect
30 | version: v1
31 | type: qdrouterd
32 | spec:
33 | containers:
34 | - name: amq-interconnect
35 | env:
36 | - name: AMQ_LOCATION_KEY
37 | value: BURR
38 | - name: ONPREM_SUFFIX
39 | value: ${ONPREM_SUFFIX}
40 | - name: AWS_SUFFIX
41 | value: ${AWS_SUFFIX}
42 | - name: GCP_SUFFIX
43 | value: ${GCP_SUFFIX}
44 | - name: AZURE_SUFFIX
45 | value: ${AZURE_SUFFIX}
46 | image: tedross/qpid-dispatch-router:1.2.0
47 | imagePullPolicy: IfNotPresent
48 | ports:
49 | - containerPort: 5672
50 | protocol: TCP
51 | volumeMounts:
52 | - name: qdr-config
53 | mountPath: /etc/qpid-dispatch
54 | - name: internal-certs
55 | readOnly: true
56 | mountPath: /etc/qpid-dispatch-certs/internal
57 | volumes:
58 | - name: qdr-config
59 | configMap:
60 | name: qdr-config
61 | - name: internal-certs
62 | secret:
63 | secretName: qdr-internal-cert
64 | ---
65 | apiVersion: v1
66 | kind: Route
67 | metadata:
68 | name: amqps
69 | spec:
70 | host: amqps-demo2-amq.apps.${ONPREM_SUFFIX}
71 | port:
72 | targetPort: amqps
73 | tls:
74 | termination: passthrough
75 | to:
76 | kind: Service
77 | name: messaging
78 | weight: 100
79 | wildcardPolicy: None
80 | ---
81 | apiVersion: v1
82 | kind: Route
83 | metadata:
84 | name: console
85 | spec:
86 | host: console-demo2-amq.apps.${ONPREM_SUFFIX}
87 | port:
88 | targetPort: console
89 | to:
90 | kind: Service
91 | name: messaging
92 | weight: 100
93 | wildcardPolicy: None
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # demo2-amq
2 | Workload, Tools, and scripts for the AMQ section of Demo-2.
3 |
4 | This demonstration shows how to deploy a distributed application into multiple OpenShift clusters
5 | in both on-premises and public-cloud data centers.
6 |
7 | The locations used in this demonstration are:
8 | - ONPREM - An on-premises cluster. This may be your laptop, if you wish.
9 | - AWS - A cluster hosted on Amazon Web Services
10 | - GCP - A cluster hosted on Google Cloud Platform
11 | - AZURE - A cluster hosted on Azure
12 |
13 | ## Setting up the Demo
14 |
15 | The first time you set up this demo, you must generate the TLS
16 | certificate authority, certificates, and keys that will be used to secure the
17 | inter-cluster connections.
18 |
19 | Note that this script uses `openssl` to generate keys and certificates.
20 |
21 | ```
22 | $ cd scripts
23 | $ ./generate-certs
24 | ```
25 |
26 | You must create environment variables in your working environment to identify the
27 | route suffixes of each of the OpenShift clusters. The route suffixes will likely
28 | be in the form ".nip.io".
29 |
30 | The environment variables are:
31 | - ONPREM_SUFFIX
32 | - AWS_SUFFIX
33 | - GCP_SUFFIX
34 | - AZURE_SUFFIX
35 |
36 | Please note that this demonstration may be run without all of the public cloud clusters
37 | in place. There must be at least one public cloud cluster and the on-premises cluster
38 | for the demonstration to be meaningful.
39 |
40 | You can now deploy the various footprints by logging into OpenShift for
41 | each cluster and running the `setup-*-all` script. Use the `Copy Login
42 | Command` feature on the OpenShift Web UI to obtain the correct login command.
43 |
44 |
45 |
46 |
47 |
48 | ```
49 | $ oc login
50 | $ ./setup-aws-all
51 |
52 | $ oc login
53 | $ ./setup-azure-all
54 |
55 | $ oc login
56 | $ ./setup-gcp-all
57 |
58 | $ oc login
59 | $ ./setup-onprem-all
60 | ```
61 |
62 | Please note that you may use `setup-*-ic-only` to deploy only the AMQ
63 | Interconnect network without the services for this demo. This might be useful
64 | if you wish to use the network for a different distributed service deployment.
65 |
66 | Once the projects are running on their respective clusters, you
67 | may access the AMQ Interconnect Console using the "console" route configured on the
68 | ONPREM cluster.
69 |
70 |
--------------------------------------------------------------------------------
/scripts/generate-certs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License
19 | #
20 |
21 | # Creates a root CA and creates password protected server and client certificates using openssl commands
22 |
23 | D=./tls-certs
24 |
25 | rm -rf $D
26 | mkdir $D
27 |
28 | ##### Create root CA #####
29 | openssl genrsa -aes256 -passout pass:ca-password -out $D/ca.key 4096
30 | openssl req -key $D/ca.key -new -x509 -days 99999 -sha256 -out $D/ca.crt -passin pass:ca-password -subj "/C=US/ST=New York/L=Brooklyn/O=Trust Me Inc./CN=Trusted.CA.com"
31 |
32 |
33 | for L in onprem azure aws gcp; do
34 | echo "$L-password" | cat > $D/tls.$L.pw
35 | openssl genrsa -aes256 -passout file:$D/tls.$L.pw -out $D/tls.$L.key 4096
36 | done
37 |
38 | openssl req -new -key $D/tls.onprem.key -passin file:$D/tls.onprem.pw -out $D/tls.onprem.csr -subj "/C=US/ST=CA/L=San Francisco/O=Red Hat Inc./CN=inter-router-demo2-amq.apps.summit.sysdeseng.com"
39 | openssl req -new -key $D/tls.azure.key -passin file:$D/tls.azure.pw -out $D/tls.azure.csr -subj "/C=US/ST=TX/L=Azure/O=Red Hat Inc./CN=inter-router-demo2-amq.apps.summit-azr.sysdeseng.com"
40 | openssl req -new -key $D/tls.aws.key -passin file:$D/tls.aws.pw -out $D/tls.aws.csr -subj "/C=US/ST=OH/L=AWS/O=Red Hat Inc./CN=inter-router-demo2-amq.apps.summit-aws.sysdeseng.com"
41 | openssl req -new -key $D/tls.gcp.key -passin file:$D/tls.gcp.pw -out $D/tls.gcp.csr -subj "/C=US/ST=OH/L=GCP/O=Red Hat Inc./CN=inter-router-demo2-amq.apps.summit-gcp.sysdeseng.com"
42 |
43 | for L in onprem azure aws gcp; do
44 | openssl x509 -req -in $D/tls.$L.csr -CA $D/ca.crt -CAkey $D/ca.key -CAcreateserial -days 9999 -out $D/tls.$L.crt -passin pass:ca-password
45 | done
46 |
47 | rm -f $D/*.csr .srl $D/ca.key
48 |
49 | for L in onprem azure aws gcp; do
50 | openssl verify -verbose -CAfile $D/ca.crt $D/tls.$L.crt
51 | done
52 |
--------------------------------------------------------------------------------
/frontend/src/main/resources/webroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AMQP Hybrid Cloud
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
32 |
33 |
34 |
35 |
36 |
AMQP Hybrid Cloud
37 |
40 |
Request
41 |
42 |
58 |
59 |
Responses
60 |
61 |
62 |
63 |
Workers
64 |
65 |
Loading
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/scripts/qdrouterd.conf.onprem:
--------------------------------------------------------------------------------
1 | ##
2 | ## Licensed to the Apache Software Foundation (ASF) under one
3 | ## or more contributor license agreements. See the NOTICE file
4 | ## distributed with this work for additional information
5 | ## regarding copyright ownership. The ASF licenses this file
6 | ## to you under the Apache License, Version 2.0 (the
7 | ## "License"); you may not use this file except in compliance
8 | ## with the License. You may obtain a copy of the License at
9 | ##
10 | ## http://www.apache.org/licenses/LICENSE-2.0
11 | ##
12 | ## Unless required by applicable law or agreed to in writing,
13 | ## software distributed under the License is distributed on an
14 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | ## KIND, either express or implied. See the License for the
16 | ## specific language governing permissions and limitations
17 | ## under the License
18 | ##
19 |
20 | # See the qdrouterd.conf (5) manual page for information about this
21 | # file's format and options.
22 |
23 | router {
24 | mode: interior
25 | id: ${AMQ_LOCATION_KEY}
26 | }
27 |
28 | sslProfile {
29 | name: TLS
30 | certFile: /etc/qpid-dispatch-certs/internal/tls.crt
31 | keyFile: /etc/qpid-dispatch-certs/internal/tls.key
32 | passwordFile: /etc/qpid-dispatch-certs/internal/tls.pw
33 | certDb: /etc/qpid-dispatch-certs/internal/ca.crt
34 | }
35 |
36 | listener {
37 | host: 0.0.0.0
38 | port: amqp
39 | authenticatePeer: no
40 | saslMechanisms: ANONYMOUS
41 | }
42 |
43 | listener {
44 | host: 0.0.0.0
45 | port: 5671
46 | authenticatePeer: no
47 | saslMechanisms: ANONYMOUS
48 | sslProfile: TLS
49 | }
50 |
51 | listener {
52 | host: 0.0.0.0
53 | port: 8672
54 | authenticatePeer: no
55 | http: yes
56 | httpRootDir: /usr/share/qpid-dispatch/console/
57 | }
58 |
59 | connector {
60 | host: inter-router-demo2-amq.${AWS_SUFFIX}
61 | port: 443
62 | role: inter-router
63 | sslProfile: TLS
64 | saslMechanisms: EXTERNAL
65 | verifyHostName: no
66 | cost: 5
67 | }
68 |
69 |
70 | connector {
71 | host: inter-router-demo2-amq.${AZURE_SUFFIX}
72 | port: 443
73 | role: inter-router
74 | sslProfile: TLS
75 | saslMechanisms: EXTERNAL
76 | verifyHostName: no
77 | cost: 6
78 | }
79 |
80 | connector {
81 | host: inter-router-demo2-amq.${GCP_SUFFIX}
82 | port: 443
83 | role: inter-router
84 | sslProfile: TLS
85 | saslMechanisms: EXTERNAL
86 | verifyHostName: no
87 | cost: 3
88 | }
89 |
90 | address {
91 | prefix: closest
92 | distribution: closest
93 | }
94 |
95 | address {
96 | prefix: multicast
97 | distribution: multicast
98 | }
99 |
100 | address {
101 | prefix: unicast
102 | distribution: closest
103 | }
104 |
105 | address {
106 | prefix: exclusive
107 | distribution: closest
108 | }
109 |
110 | address {
111 | prefix: broadcast
112 | distribution: multicast
113 | }
114 |
--------------------------------------------------------------------------------
/messaging-load/load.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | #
20 |
21 | from __future__ import print_function, unicode_literals
22 | import optparse
23 | import uuid
24 | from proton import Message
25 | from proton.handlers import MessagingHandler
26 | from proton.reactor import Container, DynamicNodeProperties
27 | import os
28 |
29 | class Client(MessagingHandler):
30 | def __init__(self, url):
31 | super(Client, self).__init__()
32 | self.uuid = str(uuid.uuid4())[0:8]
33 | self.url = url
34 | self.service_address = "work-requests"
35 | self.capacity = 10
36 | self.outstanding = 0
37 | self.request_count = 0
38 | self.locations = {}
39 | self.receiver = None
40 | self.service_sender = None
41 |
42 | def send(self):
43 | if self.outstanding >= self.capacity:
44 | return
45 | to_send = self.capacity - self.outstanding
46 | if self.service_sender.credit < to_send:
47 | to_send = self.service_sender.credit
48 | for i in range(to_send):
49 | ap = {u'uppercase': True, u'reverse': True}
50 | msg = Message(reply_to = self.reply_to,
51 | correlation_id = 0,
52 | properties = ap,
53 | body = "Load Request")
54 | self.service_sender.send(msg)
55 | self.outstanding += 1
56 | self.request_count += 1
57 |
58 | def on_start(self, event):
59 | self.container = event.container
60 | self.reactor = event.reactor
61 | self.conn = event.container.connect(self.url)
62 |
63 | def on_connection_opened(self, event):
64 | if self.receiver == None:
65 | self.receiver = event.container.create_receiver(self.conn, None, dynamic=True)
66 | self.outstanding = 0
67 |
68 | def on_link_opened(self, event):
69 | if event.receiver == self.receiver:
70 | self.reply_to = event.receiver.remote_source.address
71 | if self.service_sender == None:
72 | self.service_sender = event.container.create_sender(self.conn, self.service_address)
73 |
74 | def on_sendable(self, event):
75 | self.send()
76 |
77 | def on_message(self, event):
78 | pass
79 |
80 | def on_accepted(self, event):
81 | if event.sender == self.service_sender:
82 | self.outstanding -= 1
83 | self.send()
84 |
85 | def on_rejected(self, event):
86 | if event.sender == self.service_sender:
87 | self.outstanding -= 1
88 | self.send()
89 |
90 | def on_released(self, event):
91 | if event.sender == self.service_sender:
92 | self.outstanding -= 1
93 | self.send()
94 |
95 |
96 | try:
97 | ##
98 | ## Try to get the message bus hostname from the openshift environment
99 | ## Fall back to 127.0.0.1 (loopback)
100 | ##
101 | host = os.getenv("MESSAGING_SERVICE_HOST", "127.0.0.1")
102 | container = Container(Client(host))
103 | container.container_id = os.getenv("HOSTNAME", "client")
104 | container.run()
105 | except KeyboardInterrupt: pass
106 |
--------------------------------------------------------------------------------
/messaging-load/local_load.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | #
20 |
21 | from __future__ import print_function, unicode_literals
22 | import optparse
23 | import uuid
24 | from proton import Message
25 | from proton.handlers import MessagingHandler
26 | from proton.reactor import Container, DynamicNodeProperties
27 | import os
28 |
29 | class Client(MessagingHandler):
30 | def __init__(self, url):
31 | super(Client, self).__init__()
32 | self.uuid = str(uuid.uuid4())[0:8]
33 | self.url = url
34 | self.service_address = "work-requests"
35 | self.capacity = 10
36 | self.outstanding = 0
37 | self.request_count = 0
38 | self.locations = {}
39 | self.receiver = None
40 | self.service_sender = None
41 |
42 | def send(self):
43 | if self.outstanding >= self.capacity:
44 | return
45 | to_send = self.capacity - self.outstanding
46 | if self.service_sender.credit < to_send:
47 | to_send = self.service_sender.credit
48 | for i in range(to_send):
49 | ap = {u'uppercase': True, u'reverse': True}
50 | msg = Message(reply_to = self.reply_to,
51 | correlation_id = 0,
52 | properties = ap,
53 | body = "Load Request")
54 | self.service_sender.send(msg)
55 | self.outstanding += 1
56 | self.request_count += 1
57 |
58 | def on_start(self, event):
59 | self.container = event.container
60 | self.reactor = event.reactor
61 | self.conn = event.container.connect(self.url)
62 |
63 | def on_connection_opened(self, event):
64 | if self.receiver == None:
65 | self.receiver = event.container.create_receiver(self.conn, None, dynamic=True)
66 | self.outstanding = 0
67 |
68 | def on_link_opened(self, event):
69 | if event.receiver == self.receiver:
70 | self.reply_to = event.receiver.remote_source.address
71 | if self.service_sender == None:
72 | self.service_sender = event.container.create_sender(self.conn, self.service_address)
73 |
74 | def on_sendable(self, event):
75 | self.send()
76 |
77 | def on_message(self, event):
78 | pass
79 |
80 | def on_accepted(self, event):
81 | if event.sender == self.service_sender:
82 | self.outstanding -= 1
83 | self.send()
84 |
85 | def on_rejected(self, event):
86 | if event.sender == self.service_sender:
87 | self.outstanding -= 1
88 | self.send()
89 |
90 | def on_released(self, event):
91 | if event.sender == self.service_sender:
92 | self.outstanding -= 1
93 | self.send()
94 |
95 |
96 | try:
97 | ##
98 | ## Try to get the message bus hostname from the openshift environment
99 | ## Fall back to 127.0.0.1 (loopback)
100 | ##
101 | ## host = os.getenv("MESSAGING_SERVICE_HOST", "amqps://amqps-demo2-amq.apps.192.168.99.102.nip.io:443")
102 | host = os.getenv("MESSAGING_SERVICE_HOST", "amqps://amqps-demo2-amq.35.230.115.194.nip.io:443")
103 | container = Container(Client(host))
104 | container.container_id = os.getenv("HOSTNAME", "client")
105 | container.run()
106 | except KeyboardInterrupt: pass
107 |
--------------------------------------------------------------------------------
/controller/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AMQ Hybrid Cloud Demo Controller
5 |
6 |
7 |
8 |
9 |
10 |
AMQ Hybrid Cloud Demo Controller
11 |
12 |
Current Rates
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Load Control
21 |
22 |
23 |
24 |
Threads
Outstanding
Rate
Controls
25 |
0
0
0
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
Server Rate Limiting
36 |
37 |
38 |
39 |
Server Location
Controls
40 |
AWS
41 |
42 |
43 |
44 |
45 |
46 |
47 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/worker/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 | 4.0.0
20 |
21 | io.openshift.booster
22 | vertx-messaging-worker
23 | 1-SNAPSHOT
24 | jar
25 |
26 | Eclipse Vert.x - Messaging - Work Queue Booster - Worker
27 |
28 |
29 | io.openshift.booster.messaging.Worker
30 | /
31 | 1.8
32 | 1.8
33 | 3.5.3
34 | 3.5.40
35 | 1.0.17
36 |
37 |
38 |
39 |
40 |
41 | io.vertx
42 | vertx-dependencies
43 | ${vertx.version}
44 | pom
45 | import
46 |
47 |
48 |
49 |
50 |
51 |
52 | io.vertx
53 | vertx-proton
54 |
55 |
56 | org.slf4j
57 | slf4j-simple
58 | 1.7.25
59 |
60 |
61 | io.vertx
62 | vertx-core
63 |
64 |
65 | io.vertx
66 | vertx-rx-java2
67 |
68 |
69 | io.vertx
70 | vertx-config
71 |
72 |
73 |
74 |
75 |
76 |
77 | io.reactiverse
78 | vertx-maven-plugin
79 | ${vertx-maven-plugin.version}
80 |
81 |
82 | vmp
83 |
84 | initialize
85 | package
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | openshift
96 |
97 |
98 |
99 | io.fabric8
100 | fabric8-maven-plugin
101 | ${fabric8-maven-plugin.version}
102 |
103 |
104 | fmp
105 |
106 | resource
107 | build
108 |
109 |
110 |
111 |
112 |
113 |
114 | f8-maven-scm
115 |
116 |
117 |
118 |
119 | /
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/worker/integration-tests/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | io.openshift.booster
8 | vertx-messaging-work-queue-parent
9 | 1-SNAPSHOT
10 |
11 |
12 | vertx-messaging-booster-integration-tests
13 |
14 | Eclipse Vert.x - Messaging - Work Queue Booster - Integration Tests
15 |
16 |
17 |
18 | io.rest-assured
19 | rest-assured
20 | 3.1.0
21 | test
22 |
23 |
24 | org.assertj
25 | assertj-core
26 | 3.10.0
27 | test
28 |
29 |
30 | org.jboss.arquillian.junit
31 | arquillian-junit-standalone
32 | test
33 | 1.4.0.Final
34 |
35 |
36 | org.arquillian.cube
37 | arquillian-cube-openshift
38 | 1.16.0
39 | test
40 |
41 |
42 | org.arquillian.cube
43 | arquillian-cube-requirement
44 | 1.16.0
45 | test
46 |
47 |
48 |
49 |
50 |
51 |
52 | io.fabric8
53 | fabric8-maven-plugin
54 |
55 | true
56 |
57 |
58 |
59 | org.apache.maven.plugins
60 | maven-install-plugin
61 |
62 | true
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | openshift
71 |
72 |
73 |
74 | io.fabric8
75 | fabric8-maven-plugin
76 |
77 |
78 |
79 | resource
80 |
81 |
82 |
83 |
84 | false
85 | aggregate
86 |
87 |
88 |
89 | io.openshift.booster
90 | vertx-messaging-frontend
91 | ${project.version}
92 |
93 |
94 | io.openshift.booster
95 | vertx-messaging-worker
96 | ${project.version}
97 |
98 |
99 |
100 |
101 | org.apache.maven.plugins
102 | maven-failsafe-plugin
103 |
104 |
105 | file://${basedir}/target/test-classes/amq.yaml
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | openshift-it
114 |
115 |
116 |
117 | org.apache.maven.plugins
118 | maven-failsafe-plugin
119 |
120 |
121 |
122 | integration-test
123 | verify
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/worker/integration-tests/src/test/java/io/openshift/booster/OpenShiftIT.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Red Hat, Inc, and individual contributors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.openshift.booster;
18 |
19 | import java.net.MalformedURLException;
20 | import java.net.URL;
21 |
22 | import io.restassured.path.json.JsonPath;
23 | import io.restassured.response.Response;
24 | import org.arquillian.cube.openshift.impl.enricher.AwaitRoute;
25 | import org.arquillian.cube.openshift.impl.enricher.RouteURL;
26 | import org.arquillian.cube.openshift.impl.requirement.RequiresOpenshift;
27 | import org.arquillian.cube.requirement.ArquillianConditionalRunner;
28 | import org.junit.Before;
29 | import org.junit.Test;
30 | import org.junit.experimental.categories.Category;
31 | import org.junit.runner.RunWith;
32 |
33 | import static io.restassured.RestAssured.given;
34 | import static io.restassured.RestAssured.when;
35 | import static io.restassured.RestAssured.withArgs;
36 | import static java.util.concurrent.TimeUnit.SECONDS;
37 | import static org.assertj.core.api.Assertions.assertThat;
38 | import static org.awaitility.Awaitility.await;
39 | import static org.hamcrest.Matchers.equalTo;
40 | import static org.hamcrest.Matchers.hasItem;
41 | import static org.hamcrest.Matchers.is;
42 | import static org.hamcrest.Matchers.isEmptyString;
43 | import static org.hamcrest.Matchers.not;
44 | import static org.hamcrest.Matchers.notNullValue;
45 |
46 | @Category(RequiresOpenshift.class)
47 | @RequiresOpenshift
48 | @RunWith(ArquillianConditionalRunner.class)
49 | public class OpenShiftIT {
50 |
51 | @RouteURL("vertx-messaging-frontend")
52 | @AwaitRoute(path = "/health")
53 | private URL dashboardUrl;
54 |
55 | private URL dataUrl;
56 |
57 | private URL requestUrl;
58 |
59 | private URL responseUrl;
60 |
61 | @Before
62 | public void before() throws MalformedURLException {
63 | dataUrl = new URL(dashboardUrl, "api/data");
64 | requestUrl = new URL(dashboardUrl, "api/send-request");
65 | responseUrl = new URL(dashboardUrl, "api/receive-response");
66 | }
67 |
68 | @Test
69 | public void shouldHandleRequest() {
70 | // Issue a request
71 | Response requestResponse = given()
72 | .body("{\"text\":\"test-message\",\"uppercase\":true,\"reverse\":true}")
73 | .and()
74 | .contentType("application/json")
75 | .when()
76 | .post(requestUrl)
77 | .thenReturn();
78 |
79 | assertThat(requestResponse.getStatusCode()).isEqualTo(202);
80 | String requestId = requestResponse.getBody().asString();
81 |
82 | // Wait for the request to be handled
83 | await().atMost(10, SECONDS)
84 | .untilAsserted(() -> given()
85 | .queryParam("request", requestId)
86 | .when()
87 | .get(responseUrl)
88 | .then()
89 | .statusCode(200)
90 | .body("requestId", is(equalTo(requestId)))
91 | .body("workerId", not((isEmptyString())))
92 | .body("text", is(equalTo("EGASSEM-TSET"))));
93 |
94 | JsonPath responseJson = given()
95 | .queryParam("request", requestId)
96 | .when()
97 | .get(responseUrl)
98 | .thenReturn()
99 | .jsonPath();
100 | String workerId = responseJson.getString("workerId");
101 | String text = responseJson.getString("text");
102 |
103 | // Verify data
104 | await().atMost(10, SECONDS)
105 | .untilAsserted(() -> when()
106 | .get(dataUrl)
107 | .then()
108 | .statusCode(200)
109 | .body("requestIds", hasItem(requestId))
110 | .body("responses.%s.requestId", withArgs(requestId), is(equalTo(requestId)))
111 | .body("responses.%s.workerId", withArgs(requestId), is(equalTo(workerId)))
112 | .body("responses.%s.text", withArgs(requestId), is(equalTo(text)))
113 | .body("workers.%s.workerId", withArgs(workerId), is(equalTo(workerId)))
114 | .body("workers.%s.timestamp", withArgs(workerId), is(notNullValue()))
115 | .body("workers.%s.requestsProcessed", withArgs(workerId), is(notNullValue()))
116 | .body("workers.%s.processingErrors", withArgs(workerId), is(notNullValue())));
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/frontend/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 | 4.0.0
20 |
21 | io.openshift.booster
22 | vertx-messaging-frontend
23 | 1-SNAPSHOT
24 | jar
25 |
26 | Eclipse Vert.x - Messaging - Work Queue Booster - Frontend
27 |
28 |
29 | 1.8
30 | 1.8
31 | 3.5.3
32 | io.openshift.booster.messaging.Frontend
33 | 3.5.40
34 | 1.0.17
35 |
36 |
37 |
38 |
39 |
40 | io.vertx
41 | vertx-dependencies
42 | ${vertx.version}
43 | pom
44 | import
45 |
46 |
47 |
48 |
49 |
50 |
51 | io.vertx
52 | vertx-web
53 |
54 |
55 | io.vertx
56 | vertx-proton
57 |
58 |
59 | org.slf4j
60 | slf4j-simple
61 | 1.7.25
62 |
63 |
64 | io.vertx
65 | vertx-core
66 |
67 |
68 | io.vertx
69 | vertx-rx-java2
70 |
71 |
72 | io.vertx
73 | vertx-config
74 |
75 |
76 |
77 |
78 |
79 |
80 | io.reactiverse
81 | vertx-maven-plugin
82 | ${vertx-maven-plugin.version}
83 |
84 |
85 | vmp
86 |
87 | initialize
88 | package
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | openshift
99 |
100 |
101 |
102 | io.fabric8
103 | fabric8-maven-plugin
104 | ${fabric8-maven-plugin.version}
105 |
106 |
107 | fmp
108 |
109 | resource
110 | build
111 |
112 |
113 |
114 |
115 |
116 |
117 | f8-maven-scm
118 |
119 |
120 |
121 |
122 | /health
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/worker/integration-tests/src/test/resources/amq.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: ImageStream
3 | apiVersion: v1
4 | metadata:
5 | name: amq-broker-71-openshift
6 | annotations:
7 | openshift.io/display-name: Red Hat AMQ Broker 7.1
8 | openshift.io/provider-display-name: Red Hat, Inc.
9 | spec:
10 | lookupPolicy:
11 | local: true
12 | tags:
13 | - name: '1.0'
14 | annotations:
15 | description: Red Hat AMQ Broker 7.1 image.
16 | iconClass: icon-jboss
17 | tags: messaging,amq,jboss,xpaas
18 | supports: amq:7.1,messaging:1.0
19 | version: '1.0'
20 | from:
21 | kind: DockerImage
22 | name: registry.access.redhat.com/amq-broker-7-tech-preview/amq-broker-71-openshift:1.0
23 | ---
24 | kind: Service
25 | apiVersion: v1
26 | spec:
27 | ports:
28 | - port: 5672
29 | targetPort: 5672
30 | selector:
31 | deploymentConfig: "work-queue-broker-amq"
32 | metadata:
33 | name: "work-queue-broker-amq-amqp"
34 | labels:
35 | application: "work-queue-broker"
36 | annotations:
37 | description: The broker's AMQP port.
38 | ---
39 | kind: Service
40 | apiVersion: v1
41 | spec:
42 | ports:
43 | - port: 61613
44 | targetPort: 61613
45 | selector:
46 | deploymentConfig: "work-queue-broker-amq"
47 | metadata:
48 | name: "work-queue-broker-amq-stomp"
49 | labels:
50 | application: "work-queue-broker"
51 | annotations:
52 | description: The broker's STOMP port.
53 | ---
54 | kind: Service
55 | apiVersion: v1
56 | spec:
57 | ports:
58 | - port: 61616
59 | targetPort: 61616
60 | selector:
61 | deploymentConfig: "work-queue-broker-amq"
62 | metadata:
63 | name: "work-queue-broker-amq-tcp"
64 | labels:
65 | application: "work-queue-broker"
66 | annotations:
67 | description: The broker's OpenWire port.
68 | service.alpha.openshift.io/dependencies: '[{"name": "work-queue-broker-amq-amqp",
69 | "kind": "Service"},{"name": "work-queue-broker-amq-mqtt", "kind": "Service"},{"name":
70 | "work-queue-broker-amq-stomp", "kind": "Service"}]'
71 | ---
72 | kind: Service
73 | apiVersion: v1
74 | spec:
75 | clusterIP: None
76 | ports:
77 | - name: mesh
78 | port: 61616
79 | selector:
80 | deploymentConfig: "work-queue-broker-amq"
81 | metadata:
82 | name: "work-queue-broker-amq-mesh"
83 | labels:
84 | application: "work-queue-broker"
85 | annotations:
86 | service.alpha.kubernetes.io/tolerate-unready-endpoints: 'true'
87 | description: Supports node discovery for mesh formation.
88 | ---
89 | kind: DeploymentConfig
90 | apiVersion: v1
91 | metadata:
92 | name: "work-queue-broker-amq"
93 | labels:
94 | application: "work-queue-broker"
95 | spec:
96 | strategy:
97 | type: Rolling
98 | rollingParams:
99 | maxSurge: 0
100 | triggers:
101 | - type: ImageChange
102 | imageChangeParams:
103 | automatic: true
104 | containerNames:
105 | - "work-queue-broker-amq"
106 | from:
107 | kind: ImageStreamTag
108 | name: amq-broker-71-openshift:1.0
109 | - type: ConfigChange
110 | replicas: 1
111 | selector:
112 | deploymentConfig: "work-queue-broker-amq"
113 | template:
114 | metadata:
115 | name: "work-queue-broker-amq"
116 | labels:
117 | deploymentConfig: "work-queue-broker-amq"
118 | application: "work-queue-broker"
119 | spec:
120 | terminationGracePeriodSeconds: 60
121 | containers:
122 | - name: "work-queue-broker-amq"
123 | image: amq-broker-71-openshift
124 | imagePullPolicy: Always
125 | readinessProbe:
126 | exec:
127 | command:
128 | - "/bin/bash"
129 | - "-c"
130 | - "/opt/amq/bin/readinessProbe.sh"
131 | ports:
132 | - name: jolokia
133 | containerPort: 8778
134 | protocol: TCP
135 | - name: amqp
136 | containerPort: 5672
137 | protocol: TCP
138 | - name: mqtt
139 | containerPort: 1883
140 | protocol: TCP
141 | - name: stomp
142 | containerPort: 61613
143 | protocol: TCP
144 | - name: tcp
145 | containerPort: 61616
146 | protocol: TCP
147 | env:
148 | - name: AMQ_USER
149 | value: "work-queue"
150 | - name: AMQ_PASSWORD
151 | value: "work-queue"
152 | - name: AMQ_TRANSPORTS
153 | value: "amqp"
154 | - name: AMQ_QUEUES
155 | value: "work-queue/requests,work-queue/responses"
156 | - name: AMQ_ADDRESSES
157 | value: "work-queue/worker-updates"
158 | - name: AMQ_MESH_DISCOVERY_TYPE
159 | value: "dns"
160 | - name: AMQ_MESH_SERVICE_NAME
161 | value: "work-queue-broker-amq-mesh"
162 | - name: AMQ_MESH_SERVICE_NAMESPACE
163 | valueFrom:
164 | fieldRef:
165 | fieldPath: metadata.namespace
166 | - name: AMQ_STORAGE_USAGE_LIMIT
167 | value: "100 gb"
168 | - name: AMQ_QUEUE_MEMORY_LIMIT
169 | value: ""
170 |
--------------------------------------------------------------------------------
/frontend/src/main/resources/webroot/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Licensed to the Apache Software Foundation (ASF) under one
4 | * or more contributor license agreements. See the NOTICE file
5 | * distributed with this work for additional information
6 | * regarding copyright ownership. The ASF licenses this file
7 | * to you under the Apache License, Version 2.0 (the
8 | * "License"); you may not use this file except in compliance
9 | * with the License. You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing,
14 | * software distributed under the License is distributed on an
15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | * KIND, either express or implied. See the License for the
17 | * specific language governing permissions and limitations
18 | * under the License.
19 | *
20 | */
21 |
22 | "use strict";
23 |
24 | const gesso = new Gesso();
25 |
26 | class Application {
27 | constructor() {
28 | this.data = null;
29 |
30 | window.addEventListener("statechange", (event) => {
31 | this.renderResponses();
32 | this.renderWorkers();
33 | });
34 |
35 | window.addEventListener("load", (event) => {
36 | this.fetchDataPeriodically();
37 |
38 | $("#requests").addEventListener("submit", (event) => {
39 | this.sendRequest(event.target);
40 | this.fetchDataPeriodically();
41 | });
42 | });
43 | }
44 |
45 | fetchDataPeriodically() {
46 | gesso.fetchPeriodically("/api/data", (data) => {
47 | this.data = data;
48 | window.dispatchEvent(new Event("statechange"));
49 | });
50 | }
51 |
52 | sendRequest(form) {
53 | console.log("Sending request");
54 |
55 | let request = gesso.openRequest("POST", "/api/send-request", (event) => {
56 | if (event.target.status >= 200 && event.target.status < 300) {
57 | this.fetchDataPeriodically();
58 | }
59 | });
60 |
61 | let data = {
62 | text: form.text.value,
63 | uppercase: form.uppercase.checked,
64 | reverse: form.reverse.checked,
65 | };
66 |
67 | let json = JSON.stringify(data);
68 |
69 | request.setRequestHeader("Content-Type", "application/json");
70 | request.send(json);
71 |
72 | form.text.value = "";
73 | }
74 |
75 | renderResponses() {
76 | if (this.data.requestIds.length === 0) {
77 | return;
78 | }
79 |
80 | console.log("Rendering responses");
81 |
82 | let div = gesso.createDiv(null, "#responses");
83 |
84 | // let headings = ["Worker", "Cloud", "Response"];
85 | // let rows = [];
86 |
87 | for (let requestId of this.data.requestIds.reverse()) {
88 | let response = this.data.responses[requestId];
89 |
90 | if (response == null) {
91 | continue;
92 | }
93 |
94 | let item = gesso.createDiv(div, "response");
95 | gesso.createDiv(item, "worker", response.workerId);
96 | gesso.createDiv(item, "cloud", response.cloudId);
97 | gesso.createDiv(item, "text", response.text);
98 |
99 | // rows.push([response.workerId, response.cloudId, response.text]);
100 | }
101 | // let table = gesso.createTable(null, headings, rows, {id: "responses"});
102 | // gesso.replaceElement($("#responses"), table);
103 |
104 | gesso.replaceElement($("#responses"), div);
105 |
106 | }
107 |
108 | renderWorkers() {
109 | console.log("Rendering workers");
110 |
111 | if (Object.keys(this.data.workers).length === 0) {
112 | let div = gesso.createDiv(null, "#workers");
113 | let span = gesso.createSpan(div, "placeholder", "None");
114 |
115 | gesso.replaceElement($("#workers"), div);
116 |
117 | return;
118 | }
119 |
120 | let headings = ["ID", "Cloud", "Requests", "Errors"];
121 | // let headings = ["ID", "Cloud", "Updated", "Requests", "Errors"];
122 | let rows = [];
123 | let now = new Date().getTime();
124 |
125 | for (let workerId in this.data.workers) {
126 | let update = this.data.workers[workerId];
127 | let cloud = update.cloud;
128 | // let time = new Date(update.timestamp).toLocaleDateString();
129 | let requestsProcessed = update.requestsProcessed;
130 | let processingErrors = update.processingErrors;
131 |
132 | // rows.push([workerId, cloud, time, requestsProcessed, processingErrors]);
133 | rows.push([workerId, cloud, requestsProcessed, processingErrors]);
134 | }
135 |
136 | let table = gesso.createTable(null, headings, rows, {id: "workers"});
137 |
138 | gesso.replaceElement($("#workers"), table);
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/worker/.openshiftio/application.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Template
3 | metadata:
4 | name: vertx-messaging-worker
5 | annotations:
6 | description: This template creates a Build Configuration using an S2I builder.
7 | tags: instant-app
8 | parameters:
9 | - name: SOURCE_REPOSITORY_URL
10 | description: The source URL for the application
11 | displayName: Source URL
12 | required: true
13 | - name: SOURCE_REPOSITORY_REF
14 | description: The branch name for the application
15 | displayName: Source Branch
16 | value: master
17 | required: true
18 | - name: SOURCE_REPOSITORY_DIR
19 | description: The location within the source repo of the application
20 | displayName: Source Directory
21 | value: .
22 | required: true
23 | - name: GITHUB_WEBHOOK_SECRET
24 | description: A secret string used to configure the GitHub webhook.
25 | displayName: GitHub Webhook Secret
26 | required: true
27 | from: '[a-zA-Z0-9]{40}'
28 | generate: expression
29 |
30 | objects:
31 | - apiVersion: v1
32 | kind: ImageStream
33 | metadata:
34 | name: vertx-messaging-worker
35 | labels:
36 | booster: vertx-messaging-worker
37 | spec: {}
38 |
39 | - apiVersion: v1
40 | kind: ImageStream
41 | metadata:
42 | name: runtime-vertx-messaging-worker
43 | labels:
44 | booster: vertx-messaging-work-queue-booster
45 | spec:
46 | tags:
47 | - name: latest
48 | from:
49 | kind: DockerImage
50 | name: registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift
51 |
52 | - apiVersion: v1
53 | kind: BuildConfig
54 | metadata:
55 | name: vertx-messaging-worker
56 | labels:
57 | booster: vertx-messaging-worker
58 | spec:
59 | output:
60 | to:
61 | kind: ImageStreamTag
62 | name: vertx-messaging-worker:latest
63 | postCommit: {}
64 | resources: {}
65 | source:
66 | git:
67 | uri: ${SOURCE_REPOSITORY_URL}
68 | ref: ${SOURCE_REPOSITORY_REF}
69 | #contextDir: ${SOURCE_REPOSITORY_DIR}
70 | type: Git
71 | strategy:
72 | sourceStrategy:
73 | from:
74 | kind: ImageStreamTag
75 | name: runtime-vertx-messaging-worker:latest
76 | incremental: true
77 | env:
78 | - name: MAVEN_ARGS_APPEND
79 | value: "-pl ${SOURCE_REPOSITORY_DIR}"
80 | - name: ARTIFACT_DIR
81 | value: "${SOURCE_REPOSITORY_DIR}/target"
82 | type: Source
83 | triggers:
84 | - github:
85 | secret: ${GITHUB_WEBHOOK_SECRET}
86 | type: GitHub
87 | - type: ConfigChange
88 | - imageChange: {}
89 | type: ImageChange
90 |
91 | - apiVersion: v1
92 | kind: Service
93 | metadata:
94 | annotations:
95 | prometheus.io/scrape: "true"
96 | prometheus.io/port: "9779"
97 | labels:
98 | app: vertx-messaging-worker
99 | group: io.openshift.booster
100 | name: vertx-messaging-worker
101 | spec:
102 | ports:
103 | - name: http
104 | port: 8080
105 | protocol: TCP
106 | targetPort: 8080
107 | selector:
108 | app: vertx-messaging-worker
109 | group: io.openshift.booster
110 |
111 | - apiVersion: apps.openshift.io/v1
112 | kind: DeploymentConfig
113 | metadata:
114 | labels:
115 | app: vertx-messaging-worker
116 | group: io.openshift.booster
117 | name: vertx-messaging-worker
118 | spec:
119 | replicas: 1
120 | selector:
121 | app: vertx-messaging-worker
122 | group: io.openshift.booster
123 | template:
124 | metadata:
125 | labels:
126 | app: vertx-messaging-worker
127 | group: io.openshift.booster
128 | spec:
129 | containers:
130 | - env:
131 | - name: KUBERNETES_NAMESPACE
132 | valueFrom:
133 | fieldRef:
134 | fieldPath: metadata.namespace
135 | - name: AMQ_LOCATION_KEY
136 | value: BURR
137 | readinessProbe:
138 | httpGet:
139 | path: /
140 | port: 8080
141 | scheme: HTTP
142 | initialDelaySeconds: 10
143 | livenessProbe:
144 | httpGet:
145 | path: /
146 | port: 8080
147 | scheme: HTTP
148 | initialDelaySeconds: 180
149 | image: vertx-messaging-worker:latest
150 | imagePullPolicy: IfNotPresent
151 | name: vertx
152 | ports:
153 | - containerPort: 8080
154 | name: http
155 | protocol: TCP
156 | - containerPort: 9779
157 | name: prometheus
158 | protocol: TCP
159 | - containerPort: 8778
160 | name: jolokia
161 | protocol: TCP
162 | triggers:
163 | - type: ConfigChange
164 | - imageChangeParams:
165 | automatic: true
166 | containerNames:
167 | - vertx
168 | from:
169 | kind: ImageStreamTag
170 | name: vertx-messaging-worker:latest
171 | type: ImageChange
172 |
173 | - apiVersion: route.openshift.io/v1
174 | kind: Route
175 | metadata:
176 | labels:
177 | app: vertx-messaging-worker
178 | group: io.openshift.booster
179 | name: vertx-messaging-worker
180 | spec:
181 | port:
182 | targetPort: 8080
183 | to:
184 | kind: Service
185 | name: vertx-messaging-worker
186 |
--------------------------------------------------------------------------------
/server/service.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | #
20 |
21 | from __future__ import print_function
22 | from proton import Message
23 | from proton.handlers import MessagingHandler
24 | from proton.reactor import Container, DynamicNodeProperties
25 | from time import time
26 | import os
27 |
28 | class Timer(object):
29 | def __init__(self, parent):
30 | self.parent = parent
31 |
32 | def on_timer_task(self, event):
33 | self.parent.tick()
34 |
35 |
36 | class Request(object):
37 | def __init__(self, parent, delivery, message):
38 | self.parent = parent
39 | self.delivery = delivery
40 | self.body = message.body + "\nProcessed by service running on %s" % os.uname()[1]
41 | self.start = time()
42 | self.cid = message.correlation_id
43 | self.reply_to = message.reply_to
44 |
45 | def response(self):
46 | return Message(body = self.body, address = self.reply_to, correlation_id = self.cid, properties={'location':self.parent.location})
47 |
48 |
49 | class Service(MessagingHandler):
50 | def __init__(self, url, location, rate):
51 | super(Service, self).__init__(auto_accept = False)
52 | self.url = url
53 | self.rate = rate
54 | self.address = "FraudDetection/v1"
55 | self.control_address = "amq-demo.server-control.%s" % location
56 | self.requests = []
57 | self.can_process = self.rate / 2 # acceptances per half-second
58 | self.location = location
59 |
60 | def process_requests(self):
61 | while self.can_process > 0 and len(self.requests) > 0:
62 | request = self.requests.pop()
63 | self.anon_sender.send(request.response())
64 | self.accept(request.delivery)
65 | self.can_process -= 1
66 |
67 | def tick(self):
68 | ##
69 | ## Schedule the next half-second tick
70 | ##
71 | self.timer = self.reactor.schedule(0.5, Timer(self))
72 | self.can_process = self.rate / 2
73 |
74 | ##
75 | ## Process any pending requests
76 | ##
77 | self.process_requests()
78 |
79 | def on_start(self, event):
80 | self.container = event.container
81 | self.reactor = event.reactor
82 | self.conn = self.container.connect(self.url)
83 | self.timer = self.reactor.schedule(0.5, Timer(self))
84 | self.receiver = self.container.create_receiver(self.conn, self.address)
85 | self.control_receiver = self.container.create_receiver(self.conn, self.control_address)
86 | self.anon_sender = self.container.create_sender(self.conn, None)
87 |
88 | def handle_control_request(self, msg):
89 | opcode = msg.properties.get("opcode")
90 | rate = int(msg.properties.get("rate", 0))
91 | if opcode == "SET_RATE":
92 | if rate > 0:
93 | self.rate = rate
94 | elif opcode == "GET_RATE":
95 | pass
96 | else:
97 | return
98 |
99 | if msg.reply_to:
100 | response_properties["api"] = "amq-demo.server-control.v1"
101 | response_properties["opcode"] = opcode
102 | response_properties["rate"] = self.rate
103 | response_properties["location"] = self.location
104 | response = Message(address=msg.reply_to, correlation_id=msg.correlation_id, properties=response_properties)
105 | self.anon_sender.send(response)
106 |
107 | def on_message(self, event):
108 | if event.receiver == self.receiver:
109 | ##
110 | ## This is a new client request received on the service address
111 | ##
112 | self.requests.append(Request(self, event.delivery, event.message))
113 | self.process_requests()
114 | elif event.receiver == self.control_receiver:
115 | ##
116 | ## This is a control request
117 | ##
118 | self.handle_control_request(event.message)
119 |
120 | try:
121 | ##
122 | ## Try to get the message bus hostname from the openshift environment
123 | ## Fall back to 127.0.0.1 (loopback)
124 | ##
125 | host = os.getenv("MESSAGING_SERVICE_HOST", "127.0.0.1")
126 | location = os.getenv("AMQ_LOCATION_KEY", "On-Stage")
127 | initial_rate = int(os.getenv("AMQ_INITIAL_RATE", "200"))
128 | container = Container(Service(host, location, initial_rate))
129 | container.container_id = os.getenv("HOSTNAME", "Service")
130 | container.run()
131 | except KeyboardInterrupt: pass
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/frontend/.openshiftio/application.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Template
3 | metadata:
4 | name: vertx-messaging-frontend
5 | annotations:
6 | description: This template creates a Vert.x application sending message to an AMQP broker and receiving replies.
7 | tags: instant-app
8 | parameters:
9 | - name: SOURCE_REPOSITORY_URL
10 | description: The source URL for the application
11 | displayName: Source URL
12 | required: true
13 | - name: SOURCE_REPOSITORY_REF
14 | description: The branch name for the application
15 | displayName: Source Branch
16 | value: master
17 | required: true
18 | - name: SOURCE_REPOSITORY_DIR
19 | description: The location within the source repo of the application
20 | displayName: Source Directory
21 | value: .
22 | required: true
23 | - name: GITHUB_WEBHOOK_SECRET
24 | description: A secret string used to configure the GitHub webhook.
25 | displayName: GitHub Webhook Secret
26 | required: true
27 | from: '[a-zA-Z0-9]{40}'
28 | generate: expression
29 |
30 | objects:
31 | - apiVersion: v1
32 | kind: ImageStream
33 | metadata:
34 | name: vertx-messaging-frontend
35 | labels:
36 | booster: vertx-messaging-frontend
37 | spec: {}
38 |
39 | - apiVersion: v1
40 | kind: ImageStream
41 | metadata:
42 | name: runtime-vertx-messaging-frontend
43 | labels:
44 | booster: vertx-messaging-frontend
45 | spec:
46 | tags:
47 | - name: latest
48 | from:
49 | kind: DockerImage
50 | name: registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift
51 |
52 | - apiVersion: v1
53 | kind: BuildConfig
54 | metadata:
55 | name: vertx-messaging-frontend
56 | labels:
57 | booster: vertx-messaging-work-queue-booster
58 | spec:
59 | output:
60 | to:
61 | kind: ImageStreamTag
62 | name: vertx-messaging-frontend:latest
63 | postCommit: {}
64 | resources: {}
65 | source:
66 | git:
67 | uri: ${SOURCE_REPOSITORY_URL}
68 | ref: ${SOURCE_REPOSITORY_REF}
69 | #contextDir: ${SOURCE_REPOSITORY_DIR}
70 | type: Git
71 | strategy:
72 | sourceStrategy:
73 | from:
74 | kind: ImageStreamTag
75 | name: runtime-vertx-messaging-frontend:latest
76 | incremental: true
77 | env:
78 | - name: MAVEN_ARGS_APPEND
79 | value: "-pl ${SOURCE_REPOSITORY_DIR}"
80 | - name: ARTIFACT_DIR
81 | value: "${SOURCE_REPOSITORY_DIR}/target"
82 | type: Source
83 | triggers:
84 | - github:
85 | secret: ${GITHUB_WEBHOOK_SECRET}
86 | type: GitHub
87 | - type: ConfigChange
88 | - imageChange: {}
89 | type: ImageChange
90 |
91 | - apiVersion: v1
92 | kind: Service
93 | metadata:
94 | annotations:
95 | prometheus.io/scrape: "true"
96 | prometheus.io/port: "9779"
97 | labels:
98 | app: vertx-messaging-frontend
99 | group: io.openshift.booster
100 | name: vertx-messaging-frontend
101 | spec:
102 | ports:
103 | - name: http
104 | port: 8080
105 | protocol: TCP
106 | targetPort: 8080
107 | selector:
108 | app: vertx-messaging-frontend
109 | group: io.openshift.booster
110 |
111 | - apiVersion: apps.openshift.io/v1
112 | kind: DeploymentConfig
113 | metadata:
114 | labels:
115 | app: vertx-messaging-frontend
116 | group: io.openshift.booster
117 | name: vertx-messaging-frontend
118 | spec:
119 | replicas: 1
120 | selector:
121 | app: vertx-messaging-frontend
122 | group: io.openshift.booster
123 | template:
124 | metadata:
125 | labels:
126 | app: vertx-messaging-frontend
127 | group: io.openshift.booster
128 | spec:
129 | containers:
130 | - env:
131 | - name: KUBERNETES_NAMESPACE
132 | valueFrom:
133 | fieldRef:
134 | fieldPath: metadata.namespace
135 | readinessProbe:
136 | httpGet:
137 | path: /health
138 | port: 8080
139 | scheme: HTTP
140 | initialDelaySeconds: 10
141 | livenessProbe:
142 | httpGet:
143 | path: /health
144 | port: 8080
145 | scheme: HTTP
146 | initialDelaySeconds: 180
147 | image: vertx-messaging-frontend:latest
148 | imagePullPolicy: IfNotPresent
149 | name: vertx
150 | ports:
151 | - containerPort: 8080
152 | name: http
153 | protocol: TCP
154 | - containerPort: 9779
155 | name: prometheus
156 | protocol: TCP
157 | - containerPort: 8778
158 | name: jolokia
159 | protocol: TCP
160 | securityContext:
161 | privileged: false
162 | triggers:
163 | - type: ConfigChange
164 | - imageChangeParams:
165 | automatic: true
166 | containerNames:
167 | - vertx
168 | from:
169 | kind: ImageStreamTag
170 | name: vertx-messaging-frontend:latest
171 | type: ImageChange
172 |
173 | - apiVersion: route.openshift.io/v1
174 | kind: Route
175 | metadata:
176 | labels:
177 | app: vertx-messaging-frontend
178 | group: io.openshift.booster
179 | name: vertx-messaging-frontend
180 | spec:
181 | port:
182 | targetPort: 8080
183 | to:
184 | kind: Service
185 | name: vertx-messaging-frontend
186 |
--------------------------------------------------------------------------------
/frontend/.openshiftio/service.yaml:
--------------------------------------------------------------------------------
1 | kind: List
2 | apiVersion: v1
3 | metadata:
4 | name: amq-broker-7-image-streams
5 | annotations:
6 | description: ImageStream definitions for Red Hat AMQ Broker 7.1 Container Image
7 | openshift.io/provider-display-name: Red Hat, Inc.
8 | items:
9 | - kind: ImageStream
10 | apiVersion: v1
11 | metadata:
12 | name: amq-broker-71-openshift
13 | annotations:
14 | openshift.io/display-name: Red Hat AMQ Broker 7.1
15 | openshift.io/provider-display-name: Red Hat, Inc.
16 | spec:
17 | lookupPolicy:
18 | local: true
19 | tags:
20 | - name: '1.0'
21 | annotations:
22 | description: Red Hat AMQ Broker 7.1 image.
23 | iconClass: icon-jboss
24 | tags: messaging,amq,jboss,xpaas
25 | supports: amq:7.1,messaging:1.0
26 | version: '1.0'
27 | from:
28 | kind: DockerImage
29 | name: registry.access.redhat.com/amq-broker-7-tech-preview/amq-broker-71-openshift:1.0
30 |
31 | - kind: Service
32 | apiVersion: v1
33 | spec:
34 | ports:
35 | - port: 5672
36 | targetPort: 5672
37 | selector:
38 | deploymentConfig: "work-queue-broker-amq"
39 | metadata:
40 | name: "work-queue-broker-amq-amqp"
41 | labels:
42 | application: "work-queue-broker"
43 | annotations:
44 | description: The broker's AMQP port.
45 | - apiVersion: v1
46 | kind: Service
47 | metadata:
48 | annotations:
49 | description: The broker's console and Jolokia port.
50 | labels:
51 | application: "work-queue-broker"
52 | name: "work-queue-broker-amq-jolokia"
53 | spec:
54 | ports:
55 | - port: 8161
56 | targetPort: 8161
57 | selector:
58 | deploymentConfig: work-queue-broker-amq
59 | - kind: Service
60 | apiVersion: v1
61 | spec:
62 | ports:
63 | - port: 1883
64 | targetPort: 1883
65 | selector:
66 | deploymentConfig: "work-queue-broker-amq"
67 | metadata:
68 | name: "work-queue-broker-amq-mqtt"
69 | labels:
70 | application: "work-queue-broker"
71 | annotations:
72 | description: The broker's MQTT port.
73 | - kind: Service
74 | apiVersion: v1
75 | spec:
76 | ports:
77 | - port: 61613
78 | targetPort: 61613
79 | selector:
80 | deploymentConfig: "work-queue-broker-amq"
81 | metadata:
82 | name: "work-queue-broker-amq-stomp"
83 | labels:
84 | application: "work-queue-broker"
85 | annotations:
86 | description: The broker's STOMP port.
87 | - kind: Service
88 | apiVersion: v1
89 | spec:
90 | ports:
91 | - port: 61616
92 | targetPort: 61616
93 | selector:
94 | deploymentConfig: "work-queue-broker-amq"
95 | metadata:
96 | name: "work-queue-broker-amq-tcp"
97 | labels:
98 | application: "work-queue-broker"
99 | annotations:
100 | description: The broker's OpenWire port.
101 | service.alpha.openshift.io/dependencies: '[{"name": "work-queue-broker-amq-amqp",
102 | "kind": "Service"},{"name": "work-queue-broker-amq-mqtt", "kind": "Service"},{"name":
103 | "work-queue-broker-amq-stomp", "kind": "Service"}]'
104 | - kind: Service
105 | apiVersion: v1
106 | spec:
107 | clusterIP: None
108 | ports:
109 | - name: mesh
110 | port: 61616
111 | selector:
112 | deploymentConfig: "work-queue-broker-amq"
113 | metadata:
114 | name: "work-queue-broker-amq-mesh"
115 | labels:
116 | application: "work-queue-broker"
117 | annotations:
118 | service.alpha.kubernetes.io/tolerate-unready-endpoints: 'true'
119 | description: Supports node discovery for mesh formation.
120 | - kind: DeploymentConfig
121 | apiVersion: v1
122 | metadata:
123 | name: "work-queue-broker-amq"
124 | labels:
125 | application: "work-queue-broker"
126 | spec:
127 | strategy:
128 | type: Rolling
129 | rollingParams:
130 | maxSurge: 0
131 | triggers:
132 | - type: ImageChange
133 | imageChangeParams:
134 | automatic: true
135 | containerNames:
136 | - "work-queue-broker-amq"
137 | from:
138 | kind: ImageStreamTag
139 | name: amq-broker-71-openshift:1.0
140 | - type: ConfigChange
141 | replicas: 1
142 | selector:
143 | deploymentConfig: "work-queue-broker-amq"
144 | template:
145 | metadata:
146 | name: "work-queue-broker-amq"
147 | labels:
148 | deploymentConfig: "work-queue-broker-amq"
149 | application: "work-queue-broker"
150 | spec:
151 | terminationGracePeriodSeconds: 60
152 | containers:
153 | - name: "work-queue-broker-amq"
154 | image: amq-broker-71-openshift
155 | imagePullPolicy: Always
156 | readinessProbe:
157 | exec:
158 | command:
159 | - "/bin/bash"
160 | - "-c"
161 | - "/opt/amq/bin/readinessProbe.sh"
162 | ports:
163 | - name: jolokia
164 | containerPort: 8778
165 | protocol: TCP
166 | - name: amqp
167 | containerPort: 5672
168 | protocol: TCP
169 | - name: mqtt
170 | containerPort: 1883
171 | protocol: TCP
172 | - name: stomp
173 | containerPort: 61613
174 | protocol: TCP
175 | - name: tcp
176 | containerPort: 61616
177 | protocol: TCP
178 | env:
179 | - name: AMQ_USER
180 | value: "work-queue"
181 | - name: AMQ_PASSWORD
182 | value: "work-queue"
183 | - name: AMQ_TRANSPORTS
184 | value: "amqp"
185 | - name: AMQ_QUEUES
186 | value: "work-queue/requests,work-queue/responses"
187 | - name: AMQ_ADDRESSES
188 | value: "work-queue/worker-updates"
189 | - name: AMQ_MESH_DISCOVERY_TYPE
190 | value: "dns"
191 | - name: AMQ_MESH_SERVICE_NAME
192 | value: "work-queue-broker-amq-mesh"
193 | - name: AMQ_MESH_SERVICE_NAMESPACE
194 | valueFrom:
195 | fieldRef:
196 | fieldPath: metadata.namespace
197 | - name: AMQ_STORAGE_USAGE_LIMIT
198 | value: "100 gb"
199 | - name: AMQ_QUEUE_MEMORY_LIMIT
200 | value: ""
201 |
--------------------------------------------------------------------------------
/worker/src/main/java/io/openshift/booster/messaging/Worker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package io.openshift.booster.messaging;
19 |
20 | import io.vertx.core.Future;
21 | import io.vertx.core.logging.Logger;
22 | import io.vertx.core.logging.LoggerFactory;
23 | import io.vertx.proton.ProtonClient;
24 | import io.vertx.proton.ProtonConnection;
25 | import io.vertx.proton.ProtonReceiver;
26 | import io.vertx.proton.ProtonSender;
27 | import io.vertx.reactivex.config.ConfigRetriever;
28 | import io.vertx.reactivex.core.AbstractVerticle;
29 | import org.apache.qpid.proton.amqp.messaging.AmqpValue;
30 | import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
31 | import org.apache.qpid.proton.message.Message;
32 |
33 | import java.util.HashMap;
34 | import java.util.Map;
35 | import java.util.UUID;
36 | import java.util.concurrent.atomic.AtomicInteger;
37 |
38 | public class Worker extends AbstractVerticle {
39 | private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class);
40 | private static final String ID = "worker-vertx-" + UUID.randomUUID()
41 | .toString().substring(0, 4);
42 |
43 | private static final String AMQ_LOCATION_KEY =
44 | System.getenv().getOrDefault("AMQ_LOCATION_KEY", "burrUnknown");
45 |
46 |
47 | private static final AtomicInteger requestsProcessed = new AtomicInteger(0);
48 | private static final AtomicInteger processingErrors = new AtomicInteger(0);
49 |
50 | @Override
51 | public void start(Future future) {
52 | ConfigRetriever.create(vertx).rxGetConfig()
53 | .doOnSuccess(json -> {
54 | String amqpHost = json.getString("MESSAGING_SERVICE_HOST", "localhost");
55 | int amqpPort = json.getInteger("MESSAGING_SERVICE_PORT", 5672);
56 | String amqpUser = json.getString("MESSAGING_SERVICE_USER", "work-queue");
57 | String amqpPassword = json.getString("MESSAGING_SERVICE_PASSWORD", "work-queue");
58 |
59 | ProtonClient client = ProtonClient.create(vertx.getDelegate());
60 | client.connect(amqpHost, amqpPort, amqpUser, amqpPassword, result -> {
61 | if (result.failed()) {
62 | future.fail(result.cause());
63 | } else {
64 | ProtonConnection conn = result.result();
65 | conn.setContainer(ID);
66 | conn.open();
67 |
68 | receiveRequests(conn);
69 | sendUpdates(conn);
70 | future.complete();
71 | }
72 | });
73 | }).flatMap(x -> vertx.createHttpServer().requestHandler(req -> req.response().end("Ready")).rxListen(8080))
74 | .subscribe();
75 | }
76 |
77 | private void receiveRequests(ProtonConnection conn) {
78 | // Ordinarily, a sender or receiver is tied to a named message
79 | // source or target. By contrast, a null sender transmits
80 | // messages using an "anonymous" link and routes them to their
81 | // destination using the "to" property of the message.
82 | ProtonSender sender = conn.createSender(null);
83 |
84 | ProtonReceiver receiver = conn.createReceiver("work-requests");
85 |
86 | receiver.handler((delivery, request) -> {
87 | LOGGER.info("{0}: Receiving request {1}", ID, request);
88 | String responseBody;
89 |
90 | try {
91 | responseBody = processRequest(request);
92 | } catch (Exception e) {
93 | LOGGER.error("{0}: Failed processing message: {1}", ID, e.getMessage());
94 | processingErrors.incrementAndGet();
95 | return;
96 | }
97 |
98 | Map props = new HashMap<>();
99 | props.put("workerId", conn.getContainer());
100 | props.put("AMQ_LOCATION_KEY",AMQ_LOCATION_KEY);
101 |
102 | Message response = Message.Factory.create();
103 | response.setAddress(request.getReplyTo());
104 | response.setCorrelationId(request.getMessageId());
105 | response.setBody(new AmqpValue(responseBody));
106 | response.setApplicationProperties(new ApplicationProperties(props));
107 |
108 | sender.send(response);
109 |
110 | requestsProcessed.incrementAndGet();
111 |
112 | LOGGER.info("{0}: Sent {1}", ID, response);
113 | });
114 |
115 | sender.open();
116 | receiver.open();
117 | }
118 |
119 | private String processRequest(Message request) {
120 | Map props = request.getApplicationProperties().getValue();
121 | boolean uppercase = (boolean) props.get("uppercase");
122 | boolean reverse = (boolean) props.get("reverse");
123 | String text = (String) ((AmqpValue) request.getBody()).getValue();
124 |
125 | if (uppercase) {
126 | text = text.toUpperCase();
127 | }
128 |
129 | if (reverse) {
130 | text = new StringBuilder(text).reverse().toString();
131 | }
132 | return "Aloha " + text;
133 | // return text;
134 | }
135 |
136 | private void sendUpdates(ProtonConnection conn) {
137 | ProtonSender sender = conn.createSender("worker-updates");
138 |
139 | vertx.setPeriodic(5000, timer -> {
140 | if (conn.isDisconnected()) {
141 | vertx.cancelTimer(timer);
142 | return;
143 | }
144 |
145 | if (sender.sendQueueFull()) {
146 | return;
147 | }
148 |
149 | LOGGER.debug("{0}: Sending status update", ID);
150 |
151 | Map properties = new HashMap<>();
152 | properties.put("workerId", conn.getContainer());
153 | properties.put("AMQ_LOCATION_KEY",AMQ_LOCATION_KEY);
154 | properties.put("timestamp", System.currentTimeMillis());
155 | properties.put("requestsProcessed", (long) requestsProcessed.get());
156 | properties.put("processingErrors", (long) processingErrors.get());
157 |
158 | Message message = Message.Factory.create();
159 | message.setApplicationProperties(new ApplicationProperties(properties));
160 |
161 | sender.send(message);
162 | });
163 |
164 | sender.open();
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/client/client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | #
20 |
21 | from __future__ import print_function, unicode_literals
22 | import optparse
23 | import uuid
24 | from proton import Message
25 | from proton.handlers import MessagingHandler
26 | from proton.reactor import Container, DynamicNodeProperties
27 | import os
28 |
29 | class Timer(object):
30 | def __init__(self, parent):
31 | self.parent = parent
32 |
33 | def on_timer_task(self, event):
34 | self.parent.tick_500ms()
35 |
36 |
37 | class Client(MessagingHandler):
38 | def __init__(self, url):
39 | super(Client, self).__init__()
40 | self.uuid = str(uuid.uuid4())[0:8]
41 | self.url = url
42 | self.report_address = "multicast.amq-demo-report"
43 | self.stats_address = "multicast.amq-demo-stats"
44 | self.control_address = "amq-demo-control"
45 | self.service_address = "FraudDetection/v1"
46 | self.capacity = 0
47 | self.outstanding = 0
48 | self.samples = []
49 | self.max_samples = 4
50 | self.request_count = 0
51 | self.locations = {}
52 | self.receiver = None
53 | self.service_sender = None
54 |
55 | def send(self):
56 | if self.outstanding >= self.capacity:
57 | return
58 | to_send = self.capacity - self.outstanding
59 | if self.service_sender.credit < to_send:
60 | to_send = self.service_sender.credit
61 | for i in range(to_send):
62 | msg = Message(reply_to = self.reply_to, correlation_id = 0, body = "Client Request")
63 | self.service_sender.send(msg)
64 | self.outstanding += 1
65 | self.request_count += 1
66 |
67 | def count_received(self, location):
68 | if location not in self.locations:
69 | self.locations[location] = []
70 | for i in range(self.max_samples):
71 | self.locations[location].append(0)
72 | self.locations[location][0] += 1
73 |
74 | def send_stats_update(self):
75 | stat_map = {}
76 | for loc,samples in self.locations.items():
77 | stat_map[loc] = sum(samples) / (len(samples) * 0.5)
78 | self.locations[loc].pop()
79 | self.locations[loc].insert(0, 0)
80 | if sum(self.locations[loc]) == 0:
81 | self.locations.pop(loc)
82 | if self.stats_sender.credit > 0:
83 | msg = Message(properties={'api':'amq-demo.server-stats.v1'}, body=stat_map)
84 | dlv = self.stats_sender.send(msg)
85 | dlv.settle()
86 |
87 | def tick_500ms(self):
88 | self.timer = self.reactor.schedule(0.5, Timer(self))
89 | self.samples.append(self.request_count)
90 | self.request_count = 0
91 | if len(self.samples) > self.max_samples:
92 | self.samples.pop(0)
93 | rate = sum(self.samples) / (len(self.samples) * 0.5) # average rate/sec over the sample span
94 | report = {u'capacity': self.capacity,
95 | u'outstanding': self.outstanding,
96 | u'rate': int(rate)}
97 | if self.report_sender.credit > 0:
98 | msg = Message(properties={'api': 'amq-demo.client-report.v1'}, body=report)
99 | dlv = self.report_sender.send(msg)
100 | dlv.settle()
101 | self.send_stats_update()
102 |
103 | def on_start(self, event):
104 | self.container = event.container
105 | self.reactor = event.reactor
106 | self.conn = event.container.connect(self.url)
107 |
108 | def on_connection_opened(self, event):
109 | if self.receiver == None:
110 | self.receiver = event.container.create_receiver(self.conn, None, dynamic=True)
111 | self.control_receiver = event.container.create_receiver(self.conn, self.control_address)
112 | self.outstanding = 0
113 |
114 | def on_link_opened(self, event):
115 | if event.receiver == self.receiver:
116 | self.reply_to = event.receiver.remote_source.address
117 | if self.service_sender == None:
118 | self.service_sender = event.container.create_sender(self.conn, self.service_address)
119 | self.report_sender = event.container.create_sender(self.conn, self.report_address)
120 | self.stats_sender = event.container.create_sender(self.conn, self.stats_address)
121 | self.timer = self.reactor.schedule(0.5, Timer(self))
122 |
123 | def on_sendable(self, event):
124 | self.send()
125 |
126 | def on_message(self, event):
127 | try:
128 | if event.receiver == self.control_receiver:
129 | props = event.message.properties
130 | opcode = props.get('opcode')
131 | value = int(props.get('value', 0))
132 | if opcode == 'INC_CAPACITY':
133 | self.capacity += value
134 | if opcode == 'DEC_CAPACITY':
135 | self.capacity -= value
136 | if self.capacity < 0:
137 | self.capacity = 0
138 | elif event.receiver == self.receiver:
139 | ap = event.message.properties
140 | if 'location' in ap:
141 | self.count_received(ap['location'])
142 | except:
143 | pass
144 |
145 | def on_accepted(self, event):
146 | if event.sender == self.service_sender:
147 | self.outstanding -= 1
148 | self.send()
149 |
150 | def on_rejected(self, event):
151 | if event.sender == self.service_sender:
152 | self.outstanding -= 1
153 | self.send()
154 |
155 | def on_released(self, event):
156 | if event.sender == self.service_sender:
157 | self.outstanding -= 1
158 | self.send()
159 |
160 |
161 | try:
162 | ##
163 | ## Try to get the message bus hostname from the openshift environment
164 | ## Fall back to 127.0.0.1 (loopback)
165 | ##
166 | host = os.getenv("MESSAGING_SERVICE_HOST", "127.0.0.1")
167 | container = Container(Client(host))
168 | container.container_id = os.getenv("HOSTNAME", "client")
169 | container.run()
170 | except KeyboardInterrupt: pass
171 |
--------------------------------------------------------------------------------
/frontend/src/main/resources/webroot/app.css:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Licensed to the Apache Software Foundation (ASF) under one
4 | * or more contributor license agreements. See the NOTICE file
5 | * distributed with this work for additional information
6 | * regarding copyright ownership. The ASF licenses this file
7 | * to you under the Apache License, Version 2.0 (the
8 | * "License"); you may not use this file except in compliance
9 | * with the License. You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing,
14 | * software distributed under the License is distributed on an
15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | * KIND, either express or implied. See the License for the
17 | * specific language governing permissions and limitations
18 | * under the License.
19 | *
20 | */
21 |
22 | * {
23 | margin: 1em 0;
24 | padding: 0;
25 | box-sizing: border-box;
26 | border-style: none;
27 | }
28 |
29 | *:first-child {
30 | margin-top: 0;
31 | }
32 |
33 | *:last-child {
34 | margin-bottom: 0;
35 | }
36 |
37 | html,
38 | body,
39 | div,
40 | a,
41 | br {
42 | margin: 0;
43 | }
44 |
45 | body {
46 | font: 13pt/19pt "Source Sans Pro", sans-serif;
47 | font-weight: 400;
48 | color: rgba(0, 0, 0, 0.8);
49 | text-rendering: optimizeLegibility;
50 | }
51 |
52 | code,
53 | pre {
54 | font-family: "Ubuntu Mono", monospace;
55 | }
56 |
57 | h1 {
58 | font-size: 1em;
59 | font-weight: 400;
60 | margin-bottom: 2em;
61 | }
62 |
63 | h2 {
64 | font-weight: 400;
65 | font-size: 1.4em;
66 | margin-top: 1.4em;
67 | margin-bottom: 0.9em;
68 | }
69 |
70 | h1 a,
71 | h2 a,
72 | h3 a,
73 | h4 a,
74 | h5 a,
75 | h6 a {
76 | color: inherit;
77 | }
78 |
79 | h1.selected,
80 | h2.selected,
81 | h3.selected,
82 | h4.selected,
83 | h5.selected,
84 | h6.selected {
85 | color: rgba(127, 0, 0, 0.8);
86 | }
87 |
88 | a {
89 | text-decoration: none;
90 | color: rgba(15, 63, 127, 0.8);
91 | }
92 |
93 | a.heading-link {
94 | opacity: 0;
95 | font-weight: 300;
96 | padding-left: 0.3em;
97 | padding-right: 0.3em;
98 | }
99 |
100 | a.heading-link:hover {
101 | opacity: 1;
102 | }
103 |
104 | ol,
105 | ul,
106 | dd {
107 | padding-left: 1.2em;
108 | }
109 |
110 | ul {
111 | list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdAAAAHQBMYXlgQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABMSURBVCjPY/j//z8DFtwAxRhyuBTDQAMhDWDF9+/f/wfC2DRhVRwcHPwfhLFpwqrYxcUFjLFpwqkYlyayNJDsJLI8TVawkhVxBJMGABnYvZjQ/qzjAAAAAElFTkSuQmCC");
112 | }
113 |
114 | li,
115 | dt,
116 | dd {
117 | margin: 0;
118 | -webkit-column-break-inside: avoid;
119 | page-break-inside: avoid;
120 | }
121 |
122 | li > ul,
123 | li > ol,
124 | dd > dl {
125 | margin-top: 0;
126 | margin-bottom: 0;
127 | }
128 |
129 | li > p:last-child,
130 | dd > p:last-child {
131 | margin-bottom: 1em;
132 | }
133 |
134 | form input,
135 | form textarea,
136 | form button {
137 | font-family: inherit;
138 | font-size: inherit;
139 | border: 1px solid #ccc;
140 | margin-bottom: 0;
141 | }
142 |
143 | form textarea {
144 | width: 16em;
145 | }
146 |
147 | form button {
148 | background-color: rgb(247, 247, 247);
149 | padding: 0 0.4em;
150 | font-weight: 300;
151 | }
152 |
153 | table {
154 | border-collapse: collapse;
155 | }
156 |
157 | td,
158 | th {
159 | border-bottom: 1px solid #ddd;
160 | padding: 0.3em 0.5em 0.3em 0;
161 | vertical-align: top;
162 | text-align: left;
163 | }
164 |
165 | td em,
166 | th {
167 | font-weight: 300;
168 | font-style: normal;
169 | }
170 |
171 | hr {
172 | border-bottom: 1px solid #ddd;
173 | }
174 |
175 | code,
176 | pre {
177 | background-color: rgb(247, 247, 247);
178 | }
179 |
180 | pre {
181 | overflow: auto;
182 | padding: 1em;
183 | }
184 |
185 | blockquote {
186 | margin-left: 2em;
187 | }
188 |
189 | section {
190 | margin: 1.5em 0;
191 | }
192 |
193 | span.placeholder {
194 | font-style: italic;
195 | }
196 |
197 | .two-column {
198 | -webkit-column-count: 2;
199 | -moz-column-count: 2;
200 | column-count: 2;
201 | }
202 |
203 | .three-column {
204 | -webkit-column-count: 3;
205 | -moz-column-count: 3;
206 | column-count: 3;
207 | }
208 |
209 | .four-column {
210 | -webkit-column-count: 4;
211 | -moz-column-count: 4;
212 | column-count: 4;
213 | }
214 |
215 | .ruled {
216 | border-top: 1px solid #ddd;
217 | padding-top: 2em;
218 | }
219 |
220 | .flex {
221 | display: flex;
222 | justify-content: space-between;
223 | }
224 |
225 | .flex > section,
226 | .flex > div {
227 | flex: 1 0 0;
228 | }
229 |
230 | #-head, #-body, #-foot {
231 | margin-bottom: 0;
232 | }
233 |
234 | #-head {
235 | padding: 1em 2em;
236 | border-bottom: 1px solid #ddd;
237 | }
238 |
239 | #-body {
240 | padding: 2em;
241 | }
242 |
243 | #-foot {
244 | border-top: 1px solid #ddd;
245 | padding: 1em 2em;
246 | }
247 |
248 | #-head-content, #-body-content, #-foot-content {
249 | max-width: 640px;
250 | margin: 0 auto;
251 | }
252 |
253 | #-head-content {
254 | font-size: 0.9em;
255 | }
256 |
257 | #-foot-content {
258 | font-size: 0.8em;
259 | }
260 |
261 | #-path-navigation {
262 | margin: 0;
263 | padding: 0;
264 | display: inline;
265 | }
266 |
267 | #-path-navigation > li {
268 | display: inline;
269 | }
270 |
271 | #-path-navigation > li:after {
272 | content: " \00a0\003e\00a0\00a0 ";
273 | }
274 |
275 | #-path-navigation > li:last-child:after {
276 | content: none;
277 | }
278 |
279 | #-global-navigation {
280 | float: right;
281 | }
282 |
283 | #requests {
284 | font-size: 1.2em;
285 | display: grid;
286 | grid: "t t t t"
287 | "u r x b";
288 | grid-gap: 1em;
289 | grid-template-columns: repeat(4, 1fr);
290 | }
291 |
292 | #requests > * {
293 | margin: 0;
294 | }
295 |
296 | #requests > input[name="text"] {
297 | grid-area: t;
298 | width: 100%;
299 | padding: 0.2em 0.4em;
300 | }
301 |
302 | #requests input[type="checkbox"] {
303 | margin-right: 0.4em;
304 | }
305 |
306 | #requests > button {
307 | grid-area: b;
308 | font-size: 0.9em;
309 | padding: 0.3em;
310 | }
311 |
312 | #request-uppercase-option {
313 | grid-area: u;
314 | font-size: 0.9em;
315 | font-weight: 300;
316 | padding-top: 0.3em;
317 | }
318 |
319 | #request-reverse-option {
320 | grid-area: r;
321 | font-size: 0.9em;
322 | font-weight: 300;
323 | padding-top: 0.3em;
324 | }
325 |
326 | #responses {
327 | width: 100%;
328 | height: 10em;
329 | overflow: auto;
330 | padding: 0.2em 0.4em;
331 | border: 1px solid #ccc;
332 | }
333 |
334 | #responses > .response > .worker {
335 | display: inline-block;
336 | font-weight: 300;
337 | width: 15%;
338 | }
339 |
340 | #responses > .response > .cloud {
341 | display: inline-block;
342 | font-weight: 300;
343 | width: 15%;
344 | }
345 |
346 | #responses > .response > .text {
347 | display: inline;
348 | }
349 |
350 | #workers {
351 | width: 100%;
352 | }
353 |
354 | #workers th:nth-child(3),
355 | #workers th:nth-child(3),
356 | #workers th:nth-child(4),
357 | #workers td:nth-child(3),
358 | #workers td:nth-child(4) {
359 | text-align: right;
360 | }
361 |
362 | @media screen and (max-width: 720px) {
363 | .two-column,
364 | .three-column,
365 | .four-column {
366 | -webkit-column-count: 2;
367 | -moz-column-count: 2;
368 | column-count: 2;
369 | }
370 |
371 | .flex {
372 | display: block;
373 | }
374 |
375 | .flex > section {
376 | margin: 1.5em 0;
377 | }
378 |
379 | .flex > section:first-child {
380 | margin-top: 0;
381 | }
382 |
383 | .flex > section:last-child {
384 | margin-bottom: 0;
385 | }
386 | }
387 |
388 | @media screen and (max-width: 480px) {
389 | .two-column, .three-column, .four-column {
390 | -webkit-column-count: 1;
391 | -moz-column-count: 1;
392 | column-count: 1;
393 | }
394 | }
395 |
396 | @media print {
397 | body {
398 | font-size: 10pt;
399 | }
400 |
401 | pre {
402 | font-size: 0.9em;
403 | overflow: hidden;
404 | }
405 |
406 | #-head, #-foot {
407 | display: none;
408 | }
409 |
410 | #-body {
411 | padding: 0;
412 | }
413 | }
414 |
--------------------------------------------------------------------------------
/frontend/src/main/resources/webroot/gesso.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Licensed to the Apache Software Foundation (ASF) under one
4 | * or more contributor license agreements. See the NOTICE file
5 | * distributed with this work for additional information
6 | * regarding copyright ownership. The ASF licenses this file
7 | * to you under the Apache License, Version 2.0 (the
8 | * "License"); you may not use this file except in compliance
9 | * with the License. You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing,
14 | * software distributed under the License is distributed on an
15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | * KIND, either express or implied. See the License for the
17 | * specific language governing permissions and limitations
18 | * under the License.
19 | *
20 | */
21 |
22 | "use strict";
23 |
24 | const $ = document.querySelector.bind(document);
25 | const $$ = document.querySelectorAll.bind(document);
26 |
27 | Element.prototype.$ = function () {
28 | return this.querySelector.apply(this, arguments);
29 | };
30 |
31 | Element.prototype.$$ = function () {
32 | return this.querySelectorAll.apply(this, arguments);
33 | };
34 |
35 | class Gesso {
36 | constructor() {
37 | this._minFetchInterval = 500;
38 | this._maxFetchInterval = 60 * 1000;
39 | this._fetchStates = new Map(); // By path
40 | }
41 |
42 | openRequest(method, url, loadHandler) {
43 | let request = new XMLHttpRequest();
44 |
45 | request.open(method, url);
46 |
47 | if (loadHandler != null) {
48 | request.addEventListener("load", loadHandler);
49 | }
50 |
51 | return request;
52 | }
53 |
54 | _getFetchState(path) {
55 | let state = this._fetchStates[path];
56 |
57 | if (state == null) {
58 | state = {
59 | currentInterval: null,
60 | currentTimeoutId: null,
61 | failedAttempts: 0,
62 | etag: null,
63 | timestamp: null
64 | };
65 |
66 | this._fetchStates[path] = state;
67 | }
68 |
69 | return state;
70 | }
71 |
72 | fetch(path, dataHandler) {
73 | console.log("Fetching data from", path);
74 |
75 | let state = this._getFetchState(path);
76 |
77 | let request = this.openRequest("GET", path, (event) => {
78 | if (event.target.status >= 200 && event.target.status < 300) {
79 | state.failedAttempts = 0;
80 | state.etag = event.target.getResponseHeader("ETag");
81 |
82 | dataHandler(JSON.parse(event.target.responseText));
83 | } else if (event.target.status == 304) {
84 | state.failedAttempts = 0;
85 | }
86 |
87 | state.timestamp = new Date().getTime();
88 | });
89 |
90 | request.addEventListener("error", (event) => {
91 | console.log("Fetch failed");
92 | state.failedAttempts++;
93 | });
94 |
95 | let etag = state.etag;
96 |
97 | if (etag) {
98 | request.setRequestHeader("If-None-Match", etag);
99 | }
100 |
101 | request.send();
102 |
103 | return state;
104 | }
105 |
106 | fetchPeriodically(path, dataHandler) {
107 | let state = this._getFetchState(path);
108 |
109 | clearTimeout(state.currentTimeoutId);
110 | state.currentInterval = this._minFetchInterval;
111 |
112 | this._doFetchPeriodically(path, dataHandler, state);
113 |
114 | return state;
115 | }
116 |
117 | _doFetchPeriodically(path, dataHandler, state) {
118 | if (state.currentInterval >= this._maxFetchInterval) {
119 | setInterval(() => {
120 | this.fetch(path, dataHandler);
121 | }, this._maxFetchInterval);
122 |
123 | return;
124 | }
125 |
126 | state.currentTimeoutId = setTimeout(() => {
127 | this._doFetchPeriodically(path, dataHandler, state);
128 | }, state.currentInterval);
129 |
130 | state.currentInterval = Math.min(state.currentInterval * 2, this._maxFetchInterval);
131 |
132 | this.fetch(path, dataHandler);
133 | }
134 |
135 | parseQueryString(str) {
136 | if (str.startsWith("?")) {
137 | str = str.slice(1);
138 | }
139 |
140 | let qvars = str.split(/[&;]/);
141 | let obj = {};
142 |
143 | for (let i = 0; i < qvars.length; i++) {
144 | let [name, value] = qvars[i].split("=", 2);
145 |
146 | name = decodeURIComponent(name);
147 | value = decodeURIComponent(value);
148 |
149 | obj[name] = value;
150 | }
151 |
152 | return obj;
153 | }
154 |
155 | emitQueryString(obj) {
156 | let tokens = [];
157 |
158 | for (let name in obj) {
159 | if (!obj.hasOwnProperty(name)) {
160 | continue;
161 | }
162 |
163 | let value = obj[name];
164 |
165 | name = decodeURIComponent(name);
166 | value = decodeURIComponent(value);
167 |
168 | tokens.push(name + "=" + value);
169 | }
170 |
171 | return tokens.join(";");
172 | }
173 |
174 | createElement(parent, tag, options) {
175 | let elem = document.createElement(tag);
176 |
177 | if (parent != null) {
178 | parent.appendChild(elem);
179 | }
180 |
181 | if (options != null) {
182 | if (typeof options === "string" || typeof options === "number") {
183 | this.createText(elem, options);
184 | } else if (typeof options === "object") {
185 | if (options.hasOwnProperty("text")) {
186 | this.createText(elem, options["text"]);
187 | delete options["text"];
188 | }
189 |
190 | for (let key of Object.keys(options)) {
191 | elem.setAttribute(key, options[key]);
192 | }
193 | } else {
194 | throw `illegal argument: ${options}`;
195 | }
196 | }
197 |
198 | return elem;
199 | }
200 |
201 | createText(parent, text) {
202 | let node = document.createTextNode(text);
203 |
204 | if (parent != null) {
205 | parent.appendChild(node);
206 | }
207 |
208 | return node;
209 | }
210 |
211 | _setSelector(elem, selector) {
212 | if (selector == null) {
213 | return;
214 | }
215 |
216 | if (selector.startsWith("#")) {
217 | elem.setAttribute("id", selector.slice(1));
218 | } else {
219 | elem.setAttribute("class", selector);
220 | }
221 | }
222 |
223 | createDiv(parent, selector, options) {
224 | let elem = this.createElement(parent, "div", options);
225 |
226 | this._setSelector(elem, selector);
227 |
228 | return elem;
229 | }
230 |
231 | createSpan(parent, selector, options) {
232 | let elem = this.createElement(parent, "span", options);
233 |
234 | this._setSelector(elem, selector);
235 |
236 | return elem;
237 | }
238 |
239 | createLink(parent, href, options) {
240 | let elem = this.createElement(parent, "a", options);
241 |
242 | if (href != null) {
243 | elem.setAttribute("href", href);
244 | }
245 |
246 | return elem;
247 | }
248 |
249 | createTable(parent, headings, rows, options) {
250 | let elem = this.createElement(parent, "table", options);
251 | let thead = this.createElement(elem, "thead");
252 | let tbody = this.createElement(elem, "tbody");
253 |
254 | if (headings) {
255 | let tr = this.createElement(thead, "tr");
256 |
257 | for (let heading of headings) {
258 | this.createElement(tr, "th", heading);
259 | }
260 | }
261 |
262 | for (let row of rows) {
263 | let tr = this.createElement(tbody, "tr");
264 |
265 | for (let cell of row) {
266 | this.createElement(tr, "td", cell);
267 | }
268 | }
269 |
270 | return elem;
271 | }
272 |
273 | replaceElement(oldElement, newElement) {
274 | oldElement.parentNode.replaceChild(newElement, oldElement);
275 | }
276 |
277 | formatDuration(milliseconds) {
278 | if (milliseconds == null) {
279 | return "-";
280 | }
281 |
282 | let seconds = Math.round(milliseconds / 1000);
283 | let minutes = Math.round(milliseconds / 60 / 1000);
284 | let hours = Math.round(milliseconds / 3600 / 1000);
285 | let days = Math.round(milliseconds / 86400 / 1000);
286 | let weeks = Math.round(milliseconds / 432000 / 1000);
287 |
288 | if (weeks >= 2) {
289 | return `${weeks} weeks`;
290 | }
291 |
292 | if (days >= 2) {
293 | return `${days} days`;
294 | }
295 |
296 | if (hours >= 1) {
297 | return `${hours} hours`;
298 | }
299 |
300 | if (minutes >= 1) {
301 | return `${minutes} minutes`;
302 | }
303 |
304 | if (seconds === 1) {
305 | return "1 second";
306 | }
307 |
308 | return `${seconds} seconds`;
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/worker/service.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Template
4 | labels:
5 | template: amq-broker-71-basic
6 | xpaas: 1.4.12
7 | message: A new messaging service has been created in your project. It will handle the protocol(s) "${AMQ_PROTOCOL}". The username/password for accessing the service is ${AMQ_USER}/${AMQ_PASSWORD}.
8 | metadata:
9 | annotations:
10 | description: Application template for Red Hat AMQ brokers. These can be deployed as standalone or in a mesh. This template doesn't feature SSL support.
11 | iconClass: icon-amq
12 | openshift.io/display-name: Red Hat AMQ Broker 7.1 (Ephemeral, no SSL)
13 | openshift.io/provider-display-name: Red Hat, Inc.
14 | tags: messaging,amq,jboss
15 | template.openshift.io/documentation-url: 'https://access.redhat.com/documentation/en/red-hat-amq/'
16 | template.openshift.io/long-description: >-
17 | This template defines resources needed to develop Red Hat AMQ Broker 7.1 based application, including a deployment configuration, using ephemeral (temporary) storage. These can be deployed as standalone or in a mesh.
18 | template.openshift.io/support-url: 'https://access.redhat.com'
19 | version: 1.4.12
20 | name: amq-broker-71-basic
21 | objects:
22 | - apiVersion: v1
23 | kind: Service
24 | metadata:
25 | annotations:
26 | description: The broker's console and Jolokia port.
27 | labels:
28 | application: ${APPLICATION_NAME}
29 | name: ${APPLICATION_NAME}-amq-jolokia
30 | spec:
31 | ports:
32 | - port: 8161
33 | targetPort: 8161
34 | selector:
35 | deploymentConfig: ${APPLICATION_NAME}-amq
36 | - apiVersion: v1
37 | kind: Service
38 | metadata:
39 | annotations:
40 | description: The broker's AMQP port.
41 | labels:
42 | application: ${APPLICATION_NAME}
43 | name: ${APPLICATION_NAME}-amq-amqp
44 | spec:
45 | ports:
46 | - port: 5672
47 | targetPort: 5672
48 | selector:
49 | deploymentConfig: ${APPLICATION_NAME}-amq
50 | - apiVersion: v1
51 | kind: Service
52 | metadata:
53 | annotations:
54 | description: The broker's MQTT port.
55 | labels:
56 | application: ${APPLICATION_NAME}
57 | name: ${APPLICATION_NAME}-amq-mqtt
58 | spec:
59 | ports:
60 | - port: 1883
61 | targetPort: 1883
62 | selector:
63 | deploymentConfig: ${APPLICATION_NAME}-amq
64 | - apiVersion: v1
65 | kind: Service
66 | metadata:
67 | annotations:
68 | description: The broker's STOMP port.
69 | labels:
70 | application: ${APPLICATION_NAME}
71 | name: ${APPLICATION_NAME}-amq-stomp
72 | spec:
73 | ports:
74 | - port: 61613
75 | targetPort: 61613
76 | selector:
77 | deploymentConfig: ${APPLICATION_NAME}-amq
78 | - apiVersion: v1
79 | kind: Service
80 | metadata:
81 | annotations:
82 | description: The broker's OpenWire port.
83 | service.alpha.openshift.io/dependencies: '[{"name": "${APPLICATION_NAME}-amq-amqp",
84 | "kind": "Service"},{"name": "${APPLICATION_NAME}-amq-mqtt", "kind": "Service"},{"name":
85 | "${APPLICATION_NAME}-amq-stomp", "kind": "Service"}]'
86 | labels:
87 | application: ${APPLICATION_NAME}
88 | name: ${APPLICATION_NAME}-amq-tcp
89 | spec:
90 | ports:
91 | - port: 61616
92 | targetPort: 61616
93 | selector:
94 | deploymentConfig: ${APPLICATION_NAME}-amq
95 | - apiVersion: v1
96 | kind: DeploymentConfig
97 | metadata:
98 | labels:
99 | application: ${APPLICATION_NAME}
100 | name: ${APPLICATION_NAME}-amq
101 | spec:
102 | replicas: 1
103 | selector:
104 | deploymentConfig: ${APPLICATION_NAME}-amq
105 | strategy:
106 | rollingParams:
107 | maxSurge: 0
108 | type: Rolling
109 | template:
110 | metadata:
111 | labels:
112 | application: ${APPLICATION_NAME}
113 | deploymentConfig: ${APPLICATION_NAME}-amq
114 | name: ${APPLICATION_NAME}-amq
115 | spec:
116 | containers:
117 | - env:
118 | - name: AMQ_USER
119 | value: ${AMQ_USER}
120 | - name: AMQ_PASSWORD
121 | value: ${AMQ_PASSWORD}
122 | - name: AMQ_ROLE
123 | value: ${AMQ_ROLE}
124 | - name: AMQ_NAME
125 | value: ${AMQ_NAME}
126 | - name: AMQ_TRANSPORTS
127 | value: ${AMQ_PROTOCOL}
128 | - name: AMQ_QUEUES
129 | value: ${AMQ_QUEUES}
130 | - name: AMQ_ADDRESSES
131 | value: ${AMQ_ADDRESSES}
132 | - name: AMQ_GLOBAL_MAX_SIZE
133 | value: ${AMQ_GLOBAL_MAX_SIZE}
134 | - name: AMQ_ALLOW_ANONYMOUS
135 | value: ${AMQ_ALLOW_ANONYMOUS}
136 | image: amq-broker-71-openshift
137 | imagePullPolicy: Always
138 | readinessProbe:
139 | exec:
140 | command:
141 | - "/bin/bash"
142 | - "-c"
143 | - "/opt/amq/bin/readinessProbe.sh"
144 | name: ${APPLICATION_NAME}-amq
145 | ports:
146 | - containerPort: 8161
147 | name: console-jolokia
148 | protocol: TCP
149 | - containerPort: 5672
150 | name: amqp
151 | protocol: TCP
152 | - containerPort: 1883
153 | name: mqtt
154 | protocol: TCP
155 | - containerPort: 61613
156 | name: stomp
157 | protocol: TCP
158 | - containerPort: 61616
159 | name: artemis
160 | protocol: TCP
161 | terminationGracePeriodSeconds: 60
162 | triggers:
163 | - imageChangeParams:
164 | automatic: true
165 | containerNames:
166 | - ${APPLICATION_NAME}-amq
167 | from:
168 | kind: ImageStreamTag
169 | name: amq-broker-71-openshift:1.0
170 | namespace: ${IMAGE_STREAM_NAMESPACE}
171 | type: ImageChange
172 | - type: ConfigChange
173 | - apiVersion: v1
174 | kind: Route
175 | metadata:
176 | labels:
177 | application: ${APPLICATION_NAME}
178 | name: console
179 | spec:
180 | to:
181 | kind: Service
182 | name: ${APPLICATION_NAME}-amq-jolokia
183 | - kind: ImageStream
184 | apiVersion: v1
185 | metadata:
186 | name: amq-broker-71-openshift
187 | annotations:
188 | openshift.io/display-name: Red Hat AMQ Broker 7.1
189 | openshift.io/provider-display-name: Red Hat, Inc.
190 | spec:
191 | lookupPolicy:
192 | local: true
193 | tags:
194 | - name: '1.0'
195 | annotations:
196 | description: Red Hat AMQ Broker 7.1 image.
197 | iconClass: icon-jboss
198 | tags: messaging,amq,jboss,xpaas
199 | supports: amq:7.1,messaging:1.0
200 | version: '1.0'
201 | from:
202 | kind: DockerImage
203 | name: registry.access.redhat.com/amq-broker-7-tech-preview/amq-broker-71-openshift:1.0
204 | - apiVersion: v1
205 | kind: ConfigMap
206 | metadata:
207 | name: messaging-service
208 | data:
209 | MESSAGING_SERVICE_HOST: work-queue-broker-amq-amqp
210 | MESSAGING_SERVICE_PORT: "5672"
211 | parameters:
212 | - description: The name for the application.
213 | displayName: Application Name
214 | name: APPLICATION_NAME
215 | required: true
216 | value: broker
217 | - description: 'Protocols to configure, separated by commas. Allowed values are: `openwire`, `amqp`, `stomp`, `mqtt` and `hornetq`.'
218 | displayName: AMQ Protocols
219 | name: AMQ_PROTOCOL
220 | value: openwire,amqp,stomp,mqtt,hornetq
221 | - description: Queue names, separated by commas. These queues will be automatically created when the broker starts. If left empty, queues will be still created dynamically.
222 | displayName: Queues
223 | name: AMQ_QUEUES
224 | - description: Address names, separated by commas. These addresses will be automatically created when the broker starts. If left empty, addresses will be still created dynamically.
225 | displayName: Addresses
226 | name: AMQ_ADDRESSES
227 | - description: User name for standard broker user. It is required for connecting to the broker. If left empty, it will be generated.
228 | displayName: AMQ Username
229 | from: user[a-zA-Z0-9]{3}
230 | generate: expression
231 | name: AMQ_USER
232 | - description: Password for standard broker user. It is required for connecting to the broker. If left empty, it will be generated.
233 | displayName: AMQ Password
234 | from: '[a-zA-Z0-9]{8}'
235 | generate: expression
236 | name: AMQ_PASSWORD
237 | - description: User role for standard broker user.
238 | displayName: AMQ Role
239 | name: AMQ_ROLE
240 | value: admin
241 | - description: The name of the broker
242 | displayName: AMQ Name
243 | name: AMQ_NAME
244 | value: broker
245 | - description: "Maximum amount of memory which message data may consume (Default: Undefined, half of the system's memory)."
246 | displayName: AMQ Global Max Size
247 | name: AMQ_GLOBAL_MAX_SIZE
248 | value: 100 gb
249 | - description: "Determines whether or not the broker will allow anonymous access."
250 | displayName: AMQ Allow Anonymous
251 | name: AMQ_ALLOW_ANONYMOUS
252 | - description: Namespace in which the ImageStreams for Red Hat Middleware images are installed. These ImageStreams are normally installed in the openshift namespace. You should only need to modify this if you've installed the ImageStreams in a different namespace/project.
253 | displayName: ImageStream Namespace
254 | name: IMAGE_STREAM_NAMESPACE
255 | required: true
256 | value: openshift
257 |
258 |
--------------------------------------------------------------------------------
/frontend/src/main/java/io/openshift/booster/messaging/Frontend.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package io.openshift.booster.messaging;
19 |
20 | import io.reactivex.Completable;
21 | import io.vertx.core.Future;
22 | import io.vertx.core.json.Json;
23 | import io.vertx.core.logging.Logger;
24 | import io.vertx.core.logging.LoggerFactory;
25 | import io.vertx.proton.ProtonClient;
26 | import io.vertx.proton.ProtonConnection;
27 | import io.vertx.proton.ProtonReceiver;
28 | import io.vertx.proton.ProtonSender;
29 | import io.vertx.reactivex.CompletableHelper;
30 | import io.vertx.reactivex.config.ConfigRetriever;
31 | import io.vertx.reactivex.core.AbstractVerticle;
32 | import io.vertx.reactivex.core.impl.AsyncResultCompletable;
33 | import io.vertx.reactivex.ext.web.Router;
34 | import io.vertx.reactivex.ext.web.RoutingContext;
35 | import io.vertx.reactivex.ext.web.handler.BodyHandler;
36 | import io.vertx.reactivex.ext.web.handler.StaticHandler;
37 | import org.apache.qpid.proton.amqp.messaging.AmqpValue;
38 | import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
39 | import org.apache.qpid.proton.amqp.messaging.Source;
40 | import org.apache.qpid.proton.message.Message;
41 |
42 | import java.util.*;
43 | import java.util.concurrent.ConcurrentLinkedQueue;
44 | import java.util.concurrent.atomic.AtomicInteger;
45 |
46 | public class Frontend extends AbstractVerticle {
47 | private static final Logger LOGGER = LoggerFactory.getLogger(Frontend.class);
48 | private static final String ID = "frontend-vertx-" + UUID.randomUUID()
49 | .toString().substring(0, 4);
50 |
51 | private ProtonSender requestSender;
52 | private ProtonReceiver responseReceiver;
53 |
54 | private final AtomicInteger requestSequence = new AtomicInteger(0);
55 | private final Queue requestMessages = new ConcurrentLinkedQueue<>();
56 | private final Data data = new Data();
57 |
58 | @Override
59 | public void start(Future future) {
60 | Router router = Router.router(vertx);
61 | router.route().handler(BodyHandler.create());
62 | router.post("/api/send-request").handler(this::handleSendRequest);
63 | router.get("/api/receive-response").handler(this::handleReceiveResponse);
64 | router.get("/api/data").handler(this::handleGetData);
65 | router.get("/health").handler(rc -> rc.response().end("OK"));
66 | router.get("/*").handler(StaticHandler.create());
67 |
68 | ConfigRetriever.create(vertx).rxGetConfig()
69 | .flatMapCompletable(json -> {
70 | String amqpHost = json.getString("MESSAGING_SERVICE_HOST", "localhost");
71 | int amqpPort = json.getInteger("MESSAGING_SERVICE_PORT", 5672);
72 | //String amqpUser = json.getString("MESSAGING_SERVICE_USER", "work-queue");
73 | String amqpUser = json.getString("MESSAGING_SERVICE_USER", "");
74 | // String amqpPassword = json.getString("MESSAGING_SERVICE_PASSWORD", "work-queue");
75 | String amqpPassword = json.getString("MESSAGING_SERVICE_PASSWORD", "");
76 |
77 | String httpHost = json.getString("HTTP_HOST", "0.0.0.0");
78 | int httpPort = json.getInteger("HTTP_PORT", 8080);
79 |
80 | // AMQP
81 | ProtonClient client = ProtonClient.create(vertx.getDelegate());
82 | Future connected = Future.future();
83 | client.connect(amqpHost, amqpPort, amqpUser, amqpPassword, result -> {
84 | if (result.failed()) {
85 | LOGGER.error("MESSAGING_SERVICE_HOST " + amqpHost);
86 | LOGGER.error("MESSAGING_SERVICE_PORT " + amqpPort);
87 | connected.fail(result.cause());
88 | } else {
89 | ProtonConnection conn = result.result();
90 | conn.setContainer(ID);
91 | conn.open();
92 |
93 | sendRequests(conn);
94 | receiveWorkerUpdates(conn);
95 | pruneStaleWorkers();
96 | connected.complete();
97 | }
98 | });
99 |
100 | Completable brokerConnected = new AsyncResultCompletable(connected::setHandler);
101 | Completable serverStarted = vertx.createHttpServer()
102 | .requestHandler(router::accept)
103 | .rxListen(httpPort, httpHost)
104 | .toCompletable();
105 |
106 | return brokerConnected.andThen(serverStarted);
107 | })
108 | .subscribe(CompletableHelper.toObserver(future));
109 | }
110 |
111 | private void sendRequests(ProtonConnection conn) {
112 | requestSender = conn.createSender("work-requests");
113 |
114 | // Using a null address and setting the source dynamic tells
115 | // the remote peer to generate the reply address.
116 | responseReceiver = conn.createReceiver(null);
117 | Source source = (Source) responseReceiver.getSource();
118 | source.setDynamic(true);
119 |
120 | responseReceiver.openHandler(result -> requestSender.sendQueueDrainHandler(s -> doSendRequests()));
121 |
122 | responseReceiver.handler((delivery, message) -> {
123 | Map props = message.getApplicationProperties().getValue();
124 | String workerId = (String) props.get("workerId");
125 | String cloudId = (String) props.get("AMQ_LOCATION_KEY");
126 | String requestId = (String) message.getCorrelationId();
127 | String text = (String) ((AmqpValue) message.getBody()).getValue();
128 |
129 | /*
130 | trim down to the relevant substring
131 | */
132 |
133 | int lastIndex = workerId.lastIndexOf("-");
134 | String uniquePart = workerId.substring(lastIndex + 1);
135 | // LOGGER.info("BURR: " + uniquePart);
136 | Response response = new Response(requestId, uniquePart, cloudId, text);
137 |
138 | data.getResponses().put(response.getRequestId(), response);
139 |
140 | LOGGER.info("{0}: Received {1}", ID, response);
141 | });
142 |
143 | requestSender.open();
144 | responseReceiver.open();
145 | }
146 |
147 | private void doSendRequests() {
148 | if (responseReceiver == null) {
149 | return;
150 | }
151 |
152 | if (responseReceiver.getRemoteSource().getAddress() == null) {
153 | return;
154 | }
155 |
156 | while (!requestSender.sendQueueFull()) {
157 | Message message = requestMessages.poll();
158 |
159 | if (message == null) {
160 | break;
161 | }
162 |
163 | message.setReplyTo(responseReceiver.getRemoteSource().getAddress());
164 |
165 | requestSender.send(message);
166 |
167 | LOGGER.info("{0}: Sent {1}", ID, message);
168 | }
169 | }
170 |
171 | private void receiveWorkerUpdates(ProtonConnection conn) {
172 | ProtonReceiver receiver = conn.createReceiver("worker-updates");
173 |
174 | receiver.handler((delivery, message) -> {
175 | Map props = message.getApplicationProperties().getValue();
176 | String workerId = (String) props.get("workerId");
177 | String cloud = (String) props.get("AMQ_LOCATION_KEY");
178 | long timestamp = (long) props.get("timestamp");
179 | long requestsProcessed = (long) props.get("requestsProcessed");
180 | long processingErrors = (long) props.get("processingErrors");
181 |
182 | WorkerUpdate update = new WorkerUpdate(workerId, cloud, timestamp, requestsProcessed,
183 | processingErrors);
184 |
185 | data.getWorkers().put(update.getWorkerId(), update);
186 | });
187 |
188 | receiver.open();
189 | }
190 |
191 | private void handleSendRequest(RoutingContext rc) {
192 | String json = rc.getBodyAsString();
193 | String requestId = ID + "/" + requestSequence.incrementAndGet();
194 | Request request = Json.decodeValue(json, Request.class);
195 | Map props = new HashMap<>();
196 | props.put("uppercase", request.isUppercase());
197 | props.put("reverse", request.isReverse());
198 |
199 | Message message = Message.Factory.create();
200 | message.setMessageId(requestId);
201 | message.setAddress("work-requests");
202 | message.setBody(new AmqpValue(request.getText()));
203 | message.setApplicationProperties(new ApplicationProperties(props));
204 |
205 | requestMessages.add(message);
206 |
207 | data.getRequestIds().add(requestId);
208 |
209 | doSendRequests();
210 |
211 | rc.response().setStatusCode(202).end(requestId);
212 | }
213 |
214 | private void handleReceiveResponse(RoutingContext rc) {
215 | String value = rc.request().getParam("request");
216 |
217 | if (value == null) {
218 | rc.fail(500);
219 | return;
220 | }
221 |
222 | Response response = data.getResponses().get(value);
223 |
224 | if (response == null) {
225 | rc.response().setStatusCode(404).end();
226 | return;
227 | }
228 |
229 | rc.response()
230 | .setStatusCode(200)
231 | .putHeader("Content-Type", "application/json; charset=utf-8")
232 | .end(Json.encodePrettily(response));
233 | }
234 |
235 | private void handleGetData(RoutingContext rc) {
236 | rc.response()
237 | .putHeader("Content-Type", "application/json; charset=utf-8")
238 | .end(Json.encodePrettily(data));
239 | }
240 |
241 | private void pruneStaleWorkers() {
242 | vertx.setPeriodic(5000, timer -> {
243 | LOGGER.debug("{0}: Pruning stale workers", ID);
244 |
245 | Map workers = data.getWorkers();
246 | long now = System.currentTimeMillis();
247 |
248 | for (Map.Entry entry : workers.entrySet()) {
249 | String workerId = entry.getKey();
250 | WorkerUpdate update = entry.getValue();
251 |
252 | if (now - update.getTimestamp() > 10 * 1000) {
253 | workers.remove(workerId);
254 | LOGGER.info("{0}: Pruned {1}", ID, workerId);
255 | }
256 | }
257 | });
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/worker/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------