From 1f99d5a8a014e6ba238e24778bdad0759645f162 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Sun, 7 Apr 2024 15:26:04 +0000 Subject: [PATCH 01/30] k8s deployment files --- deploy/kubernetes/adapter.yaml | 39 +++++++++++++++++++++++ deploy/kubernetes/controller.yaml | 39 +++++++++++++++++++++++ deploy/kubernetes/frontend.yaml | 36 ++++++++++++++++++++++ deploy/kubernetes/mongodb.yaml | 36 ++++++++++++++++++++++ deploy/kubernetes/mqtt-adapter.yaml | 44 ++++++++++++++++++++++++++ deploy/kubernetes/mqtt.yaml | 48 +++++++++++++++++++++++++++++ deploy/kubernetes/socketio.yaml | 21 +++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 deploy/kubernetes/adapter.yaml create mode 100644 deploy/kubernetes/controller.yaml create mode 100644 deploy/kubernetes/frontend.yaml create mode 100644 deploy/kubernetes/mongodb.yaml create mode 100644 deploy/kubernetes/mqtt-adapter.yaml create mode 100644 deploy/kubernetes/mqtt.yaml create mode 100644 deploy/kubernetes/socketio.yaml diff --git a/deploy/kubernetes/adapter.yaml b/deploy/kubernetes/adapter.yaml new file mode 100644 index 0000000..d2d2d23 --- /dev/null +++ b/deploy/kubernetes/adapter.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adapter +spec: + replicas: 1 + selector: + matchLabels: + app: adapter + strategy: + type: Recreate # Specify the Recreate strategy + template: + metadata: + labels: + app: adapter + spec: + containers: + - name: adapter + image: oktopusp/adapter:latest + resources: + requests: + memory: 64Mi + cpu: 0.1 + limits: + memory: 128Mi + cpu: 0.2 + imagePullPolicy: IfNotPresent + env: + - name: NATS_URL + value: "nats://nats:4222" + - name: NATS_NAME + value: "adapter" + - name: NATS_VERIFY_CERTIFICATES + value: "false" + - name: MONGO_URI + value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/adapter?replicaSet=mongodb&ssl=false" + - name: CONTROLLER_ID + value: "oktopusController" + diff --git a/deploy/kubernetes/controller.yaml b/deploy/kubernetes/controller.yaml new file mode 100644 index 0000000..02d3416 --- /dev/null +++ b/deploy/kubernetes/controller.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller +spec: + replicas: 1 + selector: + matchLabels: + app: controller + strategy: + type: Recreate # Specify the Recreate strategy + template: + metadata: + labels: + app: controller + spec: + containers: + - name: controller + image: oktopusp/controller:latest + resources: + #requests: + # memory: 64Mi + # cpu: 0.5 + #limits: + # memory: 256Mi + # cpu: 1 + imagePullPolicy: IfNotPresent + env: + - name: NATS_URL + value: "nats://nats:4222" + - name: NATS_NAME + value: "adapter" + - name: NATS_VERIFY_CERTIFICATES + value: "false" + - name: MONGO_URI + value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/adapter?replicaSet=mongodb&ssl=false" + - name: REST_API_PORT + value: "8000" + diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml new file mode 100644 index 0000000..d698898 --- /dev/null +++ b/deploy/kubernetes/frontend.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + strategy: + type: Recreate # Specify the Recreate strategy + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: oktopusp/frontend:latest + ports: + - containerPort: 3000 + imagePullPolicy: IfNotPresent +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-svc +spec: + selector: + app: frontend + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 + nodePort: 30001 + type: NodePort diff --git a/deploy/kubernetes/mongodb.yaml b/deploy/kubernetes/mongodb.yaml new file mode 100644 index 0000000..aeb36b0 --- /dev/null +++ b/deploy/kubernetes/mongodb.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: mongodbcommunity.mongodb.com/v1 +kind: MongoDBCommunity +metadata: + name: mongodb +spec: + members: 3 + type: ReplicaSet + version: "6.0.5" + security: + authentication: + modes: ["SCRAM"] + users: + - name: oktopusp + db: admin + passwordSecretRef: # a reference to the secret that will be used to generate the user's password + name: mongo-secret + roles: + - name: clusterAdmin + db: admin + - name: userAdminAnyDatabase + db: admin + scramCredentialsSecretName: my-scram + additionalMongodConfig: + storage.wiredTiger.engineConfig.journalCompressor: zlib + +# the user credentials will be generated from this secret +# once the credentials are generated, this secret is no longer required +--- +apiVersion: v1 +kind: Secret +metadata: + name: mongo-secret +type: Opaque +stringData: + password: oktopusp diff --git a/deploy/kubernetes/mqtt-adapter.yaml b/deploy/kubernetes/mqtt-adapter.yaml new file mode 100644 index 0000000..3d7b7b5 --- /dev/null +++ b/deploy/kubernetes/mqtt-adapter.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mqtt-adapter +spec: + replicas: 1 + selector: + matchLabels: + app: mqtt-adapter + template: + metadata: + labels: + app: mqtt-adapter + spec: + containers: + - name: mqtt-adapter + image: oktopusp/mqtt-adapter:latest + resources: + requests: + memory: 64Mi + cpu: 0.1 + limits: + memory: 128Mi + cpu: 0.2 + imagePullPolicy: IfNotPresent + env: + - name: NATS_URL + value: "nats:4222" + - name: NATS_NAME + value: "mqtt-adapter" + - name: NATS_VERIFY_CERTIFICATES + value: "false" + - name: MQTT_URL + value: "tcp://mqtt:1883" + - name: MQTT_CLIENT_ID + value: "mqtt-adapter" + - name: MQTT_USERNAME + value: "" + - name: MQTT_PASSWORD + value: "" + - name: MQTT_QOS + value: "1" + - name: MQTT_SERVICE_HOST + value: "mqtt" diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml new file mode 100644 index 0000000..8ff06c7 --- /dev/null +++ b/deploy/kubernetes/mqtt.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: Service +metadata: + name: mqtt +spec: + selector: + app: mqtt + ports: + - protocol: TCP + port: 1883 + targetPort: 1883 + nodePort: 30000 + type: NodePort +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mqtt +spec: + replicas: 1 + selector: + matchLabels: + app: mqtt + template: + metadata: + labels: + app: mqtt + spec: + containers: + - name: mqtt + image: rogersacchelli/mqtt:latest + ports: + - containerPort: 1883 + resources: + requests: + memory: 64Mi + cpu: 0.1 + limits: + memory: 128Mi + cpu: 0.2 + imagePullPolicy: IfNotPresent + env: + - name: MQTT_PORT + value: ":1883" + - name: MQTT_TLS + value: "false" + - name: LOG_LEVEL + value: "0" # 0 - DEBUG diff --git a/deploy/kubernetes/socketio.yaml b/deploy/kubernetes/socketio.yaml new file mode 100644 index 0000000..86a9e5a --- /dev/null +++ b/deploy/kubernetes/socketio.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: socketio +spec: + replicas: 1 + selector: + matchLabels: + app: socketio + template: + metadata: + labels: + app: socketio + spec: + containers: + - name: socketio + image: oktopusp/socketio:latest + imagePullPolicy: IfNotPresent + env: + - name: NATS_URL + value: "nats:4222" From 76ddc8328a622619aed24be624cb79212c789ea1 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 9 Apr 2024 22:34:40 -0300 Subject: [PATCH 02/30] frontend actions --- .github/workflow/frontend.yaml | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflow/frontend.yaml diff --git a/.github/workflow/frontend.yaml b/.github/workflow/frontend.yaml new file mode 100644 index 0000000..bd87ef0 --- /dev/null +++ b/.github/workflow/frontend.yaml @@ -0,0 +1,43 @@ +name: Oktopus Frontend + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ dev ] + pull_request: + branches: [ dev ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./frontend/build/Dockerfile + platforms: linux/amd64 + push: true + tags: rogersacchelli/frontend:latest \ No newline at end of file From 26cb9760d63d14623b8ecc71cc58aefc31a8f773 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 9 Apr 2024 22:38:55 -0300 Subject: [PATCH 03/30] frontend wf --- .github/{workflow => workflows}/frontend.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflow => workflows}/frontend.yaml (100%) diff --git a/.github/workflow/frontend.yaml b/.github/workflows/frontend.yaml similarity index 100% rename from .github/workflow/frontend.yaml rename to .github/workflows/frontend.yaml From 79523310e11df1db8aafa10e07a7507c133c5b0e Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 10 Apr 2024 01:52:02 +0000 Subject: [PATCH 04/30] mqtt request to 256Mib --- deploy/kubernetes/mqtt.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml index 8ff06c7..b1b9b13 100644 --- a/deploy/kubernetes/mqtt.yaml +++ b/deploy/kubernetes/mqtt.yaml @@ -36,7 +36,7 @@ spec: memory: 64Mi cpu: 0.1 limits: - memory: 128Mi + memory: 256Mi cpu: 0.2 imagePullPolicy: IfNotPresent env: From 5a8cdb813e41e16dd9196c0a35471e39f46606f1 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 9 Apr 2024 23:26:14 -0300 Subject: [PATCH 05/30] build edit wf test --- .github/workflows/frontend.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index bd87ef0..5807825 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -11,7 +11,7 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" - build: + build-frontend: # The type of runner that the job will run on runs-on: ubuntu-latest @@ -36,7 +36,7 @@ jobs: name: Build and push uses: docker/build-push-action@v2 with: - context: ./ + context: ../ file: ./frontend/build/Dockerfile platforms: linux/amd64 push: true From 020f4542a767aa149799f1880d0ede861d406a09 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 9 Apr 2024 23:30:30 -0300 Subject: [PATCH 06/30] mqtt workflow --- .github/workflows/mqtt.yaml | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/mqtt.yaml diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml new file mode 100644 index 0000000..3cf9d59 --- /dev/null +++ b/.github/workflows/mqtt.yaml @@ -0,0 +1,43 @@ +name: Oktopus MQTT Brocker + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ dev ] + pull_request: + branches: [ dev ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build-mqtt: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: ../ + file: ./backend/mtp/mqtt/build/Dockerfile + platforms: linux/amd64 + push: true + tags: rogersacchelli/mqtt:latest \ No newline at end of file From 9e7a06d99366c7da4c0302c1e4571b31664d0a23 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 9 Apr 2024 23:34:14 -0300 Subject: [PATCH 07/30] fix path mqtt --- .github/workflows/mqtt.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 3cf9d59..5bb6d72 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -37,7 +37,7 @@ jobs: uses: docker/build-push-action@v2 with: context: ../ - file: ./backend/mtp/mqtt/build/Dockerfile + file: ./backend/services/mtp/mqtt/build/Dockerfile platforms: linux/amd64 push: true tags: rogersacchelli/mqtt:latest \ No newline at end of file From 8d8423d082990b10df360d8ee751b4013f58f725 Mon Sep 17 00:00:00 2001 From: Roger Date: Wed, 10 Apr 2024 17:03:59 +0000 Subject: [PATCH 08/30] Missing Files for Deployment --- deploy/kubernetes/controller.yaml | 28 ++++++++++++----- deploy/kubernetes/frontend.yaml | 8 ++++- deploy/kubernetes/socketio.yaml | 14 +++++++++ deploy/kubernetes/ws-adapter.yaml | 46 +++++++++++++++++++++++++++ deploy/kubernetes/ws.yaml | 52 +++++++++++++++++++++++++++++++ frontend/build/Dockerfile | 4 +-- 6 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 deploy/kubernetes/ws-adapter.yaml create mode 100644 deploy/kubernetes/ws.yaml diff --git a/deploy/kubernetes/controller.yaml b/deploy/kubernetes/controller.yaml index 02d3416..a4dba2b 100644 --- a/deploy/kubernetes/controller.yaml +++ b/deploy/kubernetes/controller.yaml @@ -8,7 +8,7 @@ spec: matchLabels: app: controller strategy: - type: Recreate # Specify the Recreate strategy + type: Recreate template: metadata: labels: @@ -18,12 +18,12 @@ spec: - name: controller image: oktopusp/controller:latest resources: - #requests: - # memory: 64Mi - # cpu: 0.5 - #limits: - # memory: 256Mi - # cpu: 1 + # requests: + # memory: 64Mi + # cpu: 0.5 + # limits: + # memory: 256Mi + # cpu: 1 imagePullPolicy: IfNotPresent env: - name: NATS_URL @@ -36,4 +36,18 @@ spec: value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/adapter?replicaSet=mongodb&ssl=false" - name: REST_API_PORT value: "8000" +--- +apiVersion: v1 +kind: Service +metadata: + name: controller-svc +spec: + selector: + app: controller + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + nodePort: 30003 + type: NodePort diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index d698898..bacd928 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -16,10 +16,16 @@ spec: spec: containers: - name: frontend - image: oktopusp/frontend:latest + image: rogersacchelli/frontend:latest ports: - containerPort: 3000 imagePullPolicy: IfNotPresent + env: + - name: NEXT_PUBLIC_REST_ENPOINT + value: "tcp://10.10.10.10:1234" + - name: NEXT_PUBLIC_WS_ENPOINT + value: "tcp://10.11.12.13:2343" + --- apiVersion: v1 kind: Service diff --git a/deploy/kubernetes/socketio.yaml b/deploy/kubernetes/socketio.yaml index 86a9e5a..76c730d 100644 --- a/deploy/kubernetes/socketio.yaml +++ b/deploy/kubernetes/socketio.yaml @@ -19,3 +19,17 @@ spec: env: - name: NATS_URL value: "nats:4222" +--- +apiVersion: v1 +kind: Service +metadata: + name: socketio-svc +spec: + selector: + app: socketio + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + NodePort: 30002 + type: NodePort diff --git a/deploy/kubernetes/ws-adapter.yaml b/deploy/kubernetes/ws-adapter.yaml new file mode 100644 index 0000000..3be4722 --- /dev/null +++ b/deploy/kubernetes/ws-adapter.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ws-adapter +spec: + replicas: 1 + selector: + matchLabels: + app: ws-adapter + template: + metadata: + labels: + app: ws-adapter + spec: + containers: + - name: ws-adapter + image: oktopusp/ws-adapter:latest + resources: + #requests: + # memory: 64Mi + # cpu: 0.1 + #limits: + # memory: 256Mi + # cpu: 0.2 + imagePullPolicy: IfNotPresent + env: + - name: NATS_URL + value: "nats://nats:4222" + - name: NATS_NAME + value: "ws-adapter" + - name: NATS_VERIFY_CERTIFICATES + value: "0" # 0 - DEBUG + - name: WS_TOKEN + value: "" + - name: WS_AUTH_ENABLE + value: "false" + - name: WS_ADDR + value: "ws-svc" + - name: WS_PORT + value: ":8080" + - name: WS_ROUTE + value: "/ws/controller" + - name: WS_TLS_ENABLE + value: "false" + - name: WS_SKIP_TLS_VERIFY + value: "false" diff --git a/deploy/kubernetes/ws.yaml b/deploy/kubernetes/ws.yaml new file mode 100644 index 0000000..df38bc3 --- /dev/null +++ b/deploy/kubernetes/ws.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: Service +metadata: + name: ws-svc +spec: + selector: + app: ws + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 30005 + type: NodePort +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ws +spec: + replicas: 1 + selector: + matchLabels: + app: ws + template: + metadata: + labels: + app: ws + spec: + containers: + - name: ws + image: oktopusp/ws:latest + ports: + - containerPort: 8080 + #resources: + #requests: + #memory: 64Mi + #cpu: 0.1 + #limits: + #memory: 256Mi + #cpu: 0.2 + imagePullPolicy: IfNotPresent + env: + - name: SERVER_PORT + value: ":8080" + - name: SERVER_AUTH_TOKEN + value: "" + - name: SERVER_AUTH_ENABLE + value: "false" + - name: CONTROLLER_EID + value: "oktopusController" + - name: SERVER_TLS_ENABLE + value: "false" diff --git a/frontend/build/Dockerfile b/frontend/build/Dockerfile index 5c9d862..8a9893e 100644 --- a/frontend/build/Dockerfile +++ b/frontend/build/Dockerfile @@ -2,7 +2,7 @@ FROM node:16.20.2-alpine as builder WORKDIR /app -COPY ../ . +COPY ./ ./ RUN npm install @@ -26,4 +26,4 @@ RUN chmod 755 entrypoint.sh ENTRYPOINT ["/app/entrypoint.sh"] -CMD [ "npm", "run", "start" ] \ No newline at end of file +CMD [ "npm", "run", "start" ] From f9f6921e7be09a343e80fa7e0ea2cc5538e0461b Mon Sep 17 00:00:00 2001 From: Roger Date: Fri, 12 Apr 2024 02:05:45 +0000 Subject: [PATCH 09/30] frontend and socketio --- deploy/kubernetes/frontend.yaml | 4 ++-- deploy/kubernetes/socketio.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index bacd928..78a8421 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -22,9 +22,9 @@ spec: imagePullPolicy: IfNotPresent env: - name: NEXT_PUBLIC_REST_ENPOINT - value: "tcp://10.10.10.10:1234" + value: "192.168.1.130:30003" - name: NEXT_PUBLIC_WS_ENPOINT - value: "tcp://10.11.12.13:2343" + value: "192.168.1.130:30002" --- apiVersion: v1 diff --git a/deploy/kubernetes/socketio.yaml b/deploy/kubernetes/socketio.yaml index 76c730d..a51c5fd 100644 --- a/deploy/kubernetes/socketio.yaml +++ b/deploy/kubernetes/socketio.yaml @@ -31,5 +31,5 @@ spec: - protocol: TCP port: 5000 targetPort: 5000 - NodePort: 30002 + nodePort: 30002 type: NodePort From 2ae3286e87f2e8fc8409025fb4e2911fac0111fc Mon Sep 17 00:00:00 2001 From: Roger Date: Fri, 12 Apr 2024 03:47:00 +0000 Subject: [PATCH 10/30] Fix MQTT Workflow, mongodb operator user permissions --- .github/workflows/mqtt.yaml | 8 ++++---- backend/services/mtp/mqtt/build/Dockerfile | 4 ++-- deploy/kubernetes/controller.yaml | 2 +- deploy/kubernetes/frontend.yaml | 2 +- deploy/kubernetes/mongodb.yaml | 2 ++ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 5bb6d72..13c2286 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -1,4 +1,4 @@ -name: Oktopus MQTT Brocker +name: Oktopus MQTT Broker # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch @@ -36,8 +36,8 @@ jobs: name: Build and push uses: docker/build-push-action@v2 with: - context: ../ - file: ./backend/services/mtp/mqtt/build/Dockerfile + context: ./backend/services/mtp/mqtt/ + file: ./build/Dockerfile platforms: linux/amd64 push: true - tags: rogersacchelli/mqtt:latest \ No newline at end of file + tags: rogersacchelli/mqtt:latest diff --git a/backend/services/mtp/mqtt/build/Dockerfile b/backend/services/mtp/mqtt/build/Dockerfile index 2d59c5e..622703d 100644 --- a/backend/services/mtp/mqtt/build/Dockerfile +++ b/backend/services/mtp/mqtt/build/Dockerfile @@ -1,8 +1,8 @@ FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a0fc18229 as builder WORKDIR /app -COPY ../ . +COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt cmd/mqtt/main.go FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed COPY --from=builder /app/mqtt / -ENTRYPOINT ["/mqtt"] \ No newline at end of file +ENTRYPOINT ["/mqtt"] diff --git a/deploy/kubernetes/controller.yaml b/deploy/kubernetes/controller.yaml index a4dba2b..450317d 100644 --- a/deploy/kubernetes/controller.yaml +++ b/deploy/kubernetes/controller.yaml @@ -33,7 +33,7 @@ spec: - name: NATS_VERIFY_CERTIFICATES value: "false" - name: MONGO_URI - value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/adapter?replicaSet=mongodb&ssl=false" + value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/?replicaSet=mongodb&ssl=false" - name: REST_API_PORT value: "8000" --- diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 78a8421..60a21d4 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -16,7 +16,7 @@ spec: spec: containers: - name: frontend - image: rogersacchelli/frontend:latest + image: oktopusp/frontend:latest ports: - containerPort: 3000 imagePullPolicy: IfNotPresent diff --git a/deploy/kubernetes/mongodb.yaml b/deploy/kubernetes/mongodb.yaml index aeb36b0..c8aaf12 100644 --- a/deploy/kubernetes/mongodb.yaml +++ b/deploy/kubernetes/mongodb.yaml @@ -20,6 +20,8 @@ spec: db: admin - name: userAdminAnyDatabase db: admin + - name: readWriteAnyDatabase + db: admin scramCredentialsSecretName: my-scram additionalMongodConfig: storage.wiredTiger.engineConfig.journalCompressor: zlib From a09c691e81db2d3cb10001a4131a16c2a3861daa Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 00:50:20 -0300 Subject: [PATCH 11/30] mqtt build --- .github/workflows/mqtt.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 13c2286..9666cfa 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -36,7 +36,7 @@ jobs: name: Build and push uses: docker/build-push-action@v2 with: - context: ./backend/services/mtp/mqtt/ + context: ../backend/services/mtp/mqtt/ file: ./build/Dockerfile platforms: linux/amd64 push: true From 317a682e27f132b6ed1fedc10c6de07c7a228b49 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 01:06:33 -0300 Subject: [PATCH 12/30] wf mqtt --- .github/workflows/mqtt.yaml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 9666cfa..87ec422 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -20,9 +20,6 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -32,12 +29,11 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: ../backend/services/mtp/mqtt/ - file: ./build/Dockerfile - platforms: linux/amd64 - push: true - tags: rogersacchelli/mqtt:latest + + - + name: Build Docker image + run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest -f backend/services/mtp/mqtt/build/Dockerfile . + - + name: Push Docker image to Docker Hub + run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest + From c7007538cf1658f71f76ad30ea824cd78344a7be Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 01:11:51 -0300 Subject: [PATCH 13/30] Dockerfile for mqtt fixed --- backend/services/mtp/mqtt/build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/mtp/mqtt/build/Dockerfile b/backend/services/mtp/mqtt/build/Dockerfile index 622703d..5ea1214 100644 --- a/backend/services/mtp/mqtt/build/Dockerfile +++ b/backend/services/mtp/mqtt/build/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a0fc18229 as builder WORKDIR /app -COPY . . +COPY ../ ./ RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt cmd/mqtt/main.go FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed From 2cf7dc1046f89861780c9290fdc23364b8825bdb Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 01:16:38 -0300 Subject: [PATCH 14/30] absolute path set --- .github/workflows/mqtt.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 87ec422..589cb19 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -32,7 +32,7 @@ jobs: - name: Build Docker image - run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest -f backend/services/mtp/mqtt/build/Dockerfile . + run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest -f backend/services/mtp/mqtt/build/Dockerfile backend/services/mtp/mqtt - name: Push Docker image to Docker Hub run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest From 772198b63180935e9497c5078915658cf10d80a5 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 01:28:07 -0300 Subject: [PATCH 15/30] manual workflow --- .github/workflows/frontend.yaml | 29 +++++++++++++---------------- .github/workflows/mqtt.yaml | 9 +++++---- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 5807825..23a52d7 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -3,10 +3,11 @@ name: Oktopus Frontend # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: - push: - branches: [ dev ] - pull_request: - branches: [ dev ] + workflow_dispatch: # This event triggers the workflow manually + #push: + # branches: [ dev ] + #pull_request: + # branches: [ dev ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -20,9 +21,6 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -32,12 +30,11 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: ../ - file: ./frontend/build/Dockerfile - platforms: linux/amd64 - push: true - tags: rogersacchelli/frontend:latest \ No newline at end of file + + - + name: Build Docker image + run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/frontend:latest -f frontend/build/Dockerfile frontend + - + name: Push Docker image to Docker Hub + run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest + diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 589cb19..d2cd677 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -3,10 +3,11 @@ name: Oktopus MQTT Broker # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: - push: - branches: [ dev ] - pull_request: - branches: [ dev ] + workflow_dispatch: # This event triggers the workflow manually + #push: + # branches: [ dev ] + #pull_request: + # branches: [ dev ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: From e4b81a8dc0043eac7c5edf7d03e6c8c1c181dc86 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 01:34:25 -0300 Subject: [PATCH 16/30] wf dispatch --- .github/workflows/frontend.yaml | 4 ++-- .github/workflows/mqtt.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 23a52d7..52a0c4f 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -2,8 +2,8 @@ name: Oktopus Frontend # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch -on: - workflow_dispatch: # This event triggers the workflow manually +on: + workflow_dispatch # This event triggers the workflow manually #push: # branches: [ dev ] #pull_request: diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index d2cd677..159fb63 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -3,7 +3,7 @@ name: Oktopus MQTT Broker # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: - workflow_dispatch: # This event triggers the workflow manually + workflow_dispatch # This event triggers the workflow manually #push: # branches: [ dev ] #pull_request: From 5015633ef4ae52a8a59b99db907cae172dd7a211 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Fri, 12 Apr 2024 01:36:59 -0300 Subject: [PATCH 17/30] workflow only if commit on main --- .github/workflows/frontend.yaml | 9 ++++----- .github/workflows/mqtt.yaml | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 52a0c4f..29831d2 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -3,11 +3,10 @@ name: Oktopus Frontend # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: - workflow_dispatch # This event triggers the workflow manually - #push: - # branches: [ dev ] - #pull_request: - # branches: [ dev ] + push: + branches: [ main ] + pull_request: + branches: [ main ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml index 159fb63..a3d5d91 100644 --- a/.github/workflows/mqtt.yaml +++ b/.github/workflows/mqtt.yaml @@ -3,11 +3,10 @@ name: Oktopus MQTT Broker # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: - workflow_dispatch # This event triggers the workflow manually - #push: - # branches: [ dev ] - #pull_request: - # branches: [ dev ] + push: + branches: [ main ] + pull_request: + branches: [ main ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: From 9ff737855ee441b6f851dd9d05f8af299b80fcf4 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 16 Apr 2024 16:38:46 -0300 Subject: [PATCH 18/30] feat: nats volume with persistent storage --- deploy/compose/docker-compose.yaml | 2 ++ deploy/compose/nats_data/.gitkeep | 0 2 files changed, 2 insertions(+) create mode 100644 deploy/compose/nats_data/.gitkeep diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index 12b4d38..b2433ac 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -8,6 +8,8 @@ services: - 4222:4222 - 8222:8222 command: -n oktopus -m 8222 -js + volumes: + - ./nats_data:/tmp/nats/jetstream networks: usp_network: ipv4_address: 172.16.235.2 diff --git a/deploy/compose/nats_data/.gitkeep b/deploy/compose/nats_data/.gitkeep new file mode 100644 index 0000000..e69de29 From be5a69f37cb9d934a49e85f8b8243eea0b22217a Mon Sep 17 00:00:00 2001 From: Roger Date: Tue, 16 Apr 2024 23:59:55 +0000 Subject: [PATCH 19/30] Config Map for HAProxy Ingress Controller --- backend/services/mtp/mqtt/build/Dockerfile | 2 +- deploy/kubernetes/README.md | 16 ++++++++++++++++ deploy/kubernetes/frontend.yaml | 8 ++++---- .../haproxy-kubernetes-ingress-cm.yaml | 7 +++++++ deploy/kubernetes/haproxy-tcp-services-cm.yaml | 8 ++++++++ deploy/kubernetes/haproxy-tcp-services.yaml | 8 ++++++++ deploy/kubernetes/mqtt.yaml | 5 ++--- frontend/.env | 10 +++++----- frontend/build/entrypoint.sh | 14 +++++++------- 9 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 deploy/kubernetes/README.md create mode 100644 deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml create mode 100644 deploy/kubernetes/haproxy-tcp-services-cm.yaml create mode 100644 deploy/kubernetes/haproxy-tcp-services.yaml diff --git a/backend/services/mtp/mqtt/build/Dockerfile b/backend/services/mtp/mqtt/build/Dockerfile index 622703d..7025ca2 100644 --- a/backend/services/mtp/mqtt/build/Dockerfile +++ b/backend/services/mtp/mqtt/build/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a WORKDIR /app COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt cmd/mqtt/main.go - +CMD pwd FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed COPY --from=builder /app/mqtt / ENTRYPOINT ["/mqtt"] diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md new file mode 100644 index 0000000..bdc26ac --- /dev/null +++ b/deploy/kubernetes/README.md @@ -0,0 +1,16 @@ +# Oktopus Kubernetes + +## Requirements + +### Standalone Installation + +Node: +* 8 vCPUs +* 8 GB RAM + +# Installation + + +## MongoBD + + diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 60a21d4..4e61bba 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -16,14 +16,14 @@ spec: spec: containers: - name: frontend - image: oktopusp/frontend:latest + image: rogersacchelli/frontend:1.0.3 ports: - containerPort: 3000 - imagePullPolicy: IfNotPresent + imagePullPolicy: Always env: - - name: NEXT_PUBLIC_REST_ENPOINT + - name: NEXT_PUBLIC_REST_ENDPOINT value: "192.168.1.130:30003" - - name: NEXT_PUBLIC_WS_ENPOINT + - name: NEXT_PUBLIC_WS_ENDPOINT value: "192.168.1.130:30002" --- diff --git a/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml b/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml new file mode 100644 index 0000000..c4aabd9 --- /dev/null +++ b/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: haproxy-kubernetes-ingress + namespace: haproxy-controller +data: + syslog-server: "address:stdout, format: raw, facility:daemon" diff --git a/deploy/kubernetes/haproxy-tcp-services-cm.yaml b/deploy/kubernetes/haproxy-tcp-services-cm.yaml new file mode 100644 index 0000000..a4b8f7f --- /dev/null +++ b/deploy/kubernetes/haproxy-tcp-services-cm.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: haproxy-tcp + namespace: default +data: + 1883: # Port where the frontend is going to listen to. + default/mqtt-svc:1883 # Kubernetes service in the format NS/ServiceName:ServicePort diff --git a/deploy/kubernetes/haproxy-tcp-services.yaml b/deploy/kubernetes/haproxy-tcp-services.yaml new file mode 100644 index 0000000..77db1d6 --- /dev/null +++ b/deploy/kubernetes/haproxy-tcp-services.yaml @@ -0,0 +1,8 @@ +controller: + service: + tcpPorts: + - name: mqtt + port: 1883 + targetPort: 1883 + extraArgs: + - --configmap-tcp-services=default/haproxy-tcp diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml index b1b9b13..8d78cb6 100644 --- a/deploy/kubernetes/mqtt.yaml +++ b/deploy/kubernetes/mqtt.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: mqtt + name: mqtt-svc spec: selector: app: mqtt @@ -9,8 +9,7 @@ spec: - protocol: TCP port: 1883 targetPort: 1883 - nodePort: 30000 - type: NodePort + type: ClusterIP --- apiVersion: apps/v1 kind: Deployment diff --git a/frontend/.env b/frontend/.env index 32e4aa9..141984e 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,17 +1,17 @@ # ----------------------------- Local Environment ---------------------------- # -NEXT_PUBLIC_REST_ENPOINT="http://localhost:8000/api" -NEXT_PUBLIC_WS_ENPOINT="http://localhost:5000/" +NEXT_PUBLIC_REST_ENDPOINT="http://localhost:8000/api" +NEXT_PUBLIC_WS_ENDPOINT="http://localhost:5000/" # ---------------------------------------------------------------------------- # # -------------------------- Production Environment -------------------------- # -#NEXT_PUBLIC_REST_ENPOINT="https://demo.oktopus.app.br/api" -#NEXT_PUBLIC_WS_ENPOINT="https://demo.oktopus.app.br/" +#NEXT_PUBLIC_REST_ENDPOINT="https://demo.oktopus.app.br/api" +#NEXT_PUBLIC_WS_ENDPOINT="https://demo.oktopus.app.br/" # ---------------------------------------------------------------------------- # # ---------------------------- Mocked Environment ---------------------------- # -#NEXT_PUBLIC_REST_ENPOINT="https://d9962fd9-2464-4a30-9a86-a15a04b57ad0.mock.pstmn.io" +#NEXT_PUBLIC_REST_ENDPOINT="https://d9962fd9-2464-4a30-9a86-a15a04b57ad0.mock.pstmn.io" # ---------------------------------------------------------------------------- # diff --git a/frontend/build/entrypoint.sh b/frontend/build/entrypoint.sh index f4abca7..bf43c58 100644 --- a/frontend/build/entrypoint.sh +++ b/frontend/build/entrypoint.sh @@ -3,16 +3,16 @@ set -Ex function apply_path { - echo "Check that we have NEXT_PUBLIC_REST_ENPOINT vars" - test -n "$NEXT_PUBLIC_REST_ENPOINT" + echo "Check that we have NEXT_PUBLIC_REST_ENDPOINT vars" + test -n "$NEXT_PUBLIC_REST_ENDPOINT" - echo "Check that we have NEXT_PUBLIC_WS_ENPOINT vars" - test -n "$NEXT_PUBLIC_WS_ENPOINT" + echo "Check that we have NEXT_PUBLIC_WS_ENDPOINT vars" + test -n "$NEXT_PUBLIC_WS_ENDPOINT" - find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#REST_API_URL#$NEXT_PUBLIC_REST_ENPOINT#g" - find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#WS_URL#$NEXT_PUBLIC_WS_ENPOINT#g" + find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#REST_API_URL#$NEXT_PUBLIC_REST_ENDPOINT#g" + find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#WS_URL#$NEXT_PUBLIC_WS_ENDPOINT#g" } apply_path echo "Starting Nextjs" -exec "$@" \ No newline at end of file +exec "$@" From 61f93f49e5aeb1dd52cabee1bcc7381711f74e50 Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 16 Apr 2024 22:56:47 -0300 Subject: [PATCH 20/30] Kubernetes Deployment - WIP --- backend/services/mtp/mqtt/build/Dockerfile | 2 +- deploy/kubernetes/README.md | 77 +++++++++++++++++++++- deploy/kubernetes/backend.yaml | 21 ------ deploy/kubernetes/frontend.yaml | 18 +++-- deploy/kubernetes/mqtt.yaml | 7 +- deploy/kubernetes/ws.yaml | 14 ++-- frontend/build/Dockerfile | 2 +- 7 files changed, 101 insertions(+), 40 deletions(-) delete mode 100644 deploy/kubernetes/backend.yaml diff --git a/backend/services/mtp/mqtt/build/Dockerfile b/backend/services/mtp/mqtt/build/Dockerfile index cb80675..5ea1214 100644 --- a/backend/services/mtp/mqtt/build/Dockerfile +++ b/backend/services/mtp/mqtt/build/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a WORKDIR /app COPY ../ ./ RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt cmd/mqtt/main.go -CMD pwd + FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed COPY --from=builder /app/mqtt / ENTRYPOINT ["/mqtt"] diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index bdc26ac..9ef30ea 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -2,15 +2,90 @@ ## Requirements +Kubernetes 1.28+ + ### Standalone Installation -Node: +Single Node: * 8 vCPUs * 8 GB RAM # Installation +## Download Files + +```shell +git clone https://github.com/OktopUSP/oktopus +export DEPLOYMENT_PATH=oktopus/deploy/kubernetes +``` + ## MongoBD +```shell +# Mongo DB Operator at mongodb namespace +helm repo add mongodb https://mongodb.github.io/helm-charts + +helm install community-operator mongodb/community-operator --namespace mongodb --create-namespace + +# Mongo DB ReplicaSet +export DEPLOYMENT_PATH=oktopus/deploy/kubernetes + +kubectl apply -f $DEPLOYMENT_PATH/mongodb.yaml -n mongodb + +# Check Installation +kubectl get pods -n mongodb +``` + +## NATS Server + +```shell +# Download the NATS charts +helm repo add nats https://nats-io.github.io/k8s/helm/charts/ + +# Install NATS with Jetstream Enabled +helm install nats nats/nats --set config.jetstream.enabled=true +``` +## Oktopus + + +Node Ports + +For this deployment, we are not using a load balancer and kubernetes is deployed on-premises so we are using Nodeports to insource the client traffic into cluster. below the ports set on deployment files: + +1. MQTT broker service (mqtt-svc): 30000 +2. Frontend (frontend-svc): 30001 +3. SocketIO: (socketio-svc): 30002 +4. Controller (controller-svc): 30003 +5. WebSocket (ws-svc): 30005 + +Before deploying the files, edit the frontend.yaml file to set the correct enviroment variables: + +```yaml +env: + - name: NEXT_PUBLIC_REST_ENDPOINT + value: ":30003" + - name: NEXT_PUBLIC_WS_ENDPOINT + value: ":30005" +``` + +```shell +kubectl apply -f $DEPLOYMENT_PATH/mqtt.yaml +kubectl apply -f $DEPLOYMENT_PATH/mqtt-adapter.yaml +kubectl apply -f $DEPLOYMENT_PATH/adapter.yaml +kubectl apply -f $DEPLOYMENT_PATH/controller.yaml +kubectl apply -f $DEPLOYMENT_PATH/socketio.yaml +kubectl apply -f $DEPLOYMENT_PATH/frontend.yaml +kubectl apply -f $DEPLOYMENT_PATH/ws.yaml +kubectl apply -f $DEPLOYMENT_PATH/ws-adapter.yaml +``` + +### Checking cluster status: + +```shell + +kubectl get pods +kubectl get svc + +``` \ No newline at end of file diff --git a/deploy/kubernetes/backend.yaml b/deploy/kubernetes/backend.yaml deleted file mode 100644 index 10a45db..0000000 --- a/deploy/kubernetes/backend.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: backend - labels: - app: backend -spec: - replicas: 1 - selector: - matchLabels: - app: backend - template: - metadata: - labels: - app: backend - spec: - containers: - - name: backend - image: backend:latest - ports: - - containerPort: 8080 \ No newline at end of file diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml index 4e61bba..6731899 100644 --- a/deploy/kubernetes/frontend.yaml +++ b/deploy/kubernetes/frontend.yaml @@ -8,7 +8,7 @@ spec: matchLabels: app: frontend strategy: - type: Recreate # Specify the Recreate strategy + type: Recreate # Specify the Recreate strategy template: metadata: labels: @@ -16,16 +16,22 @@ spec: spec: containers: - name: frontend - image: rogersacchelli/frontend:1.0.3 - ports: + image: oktopusp/frontend:latest + resources: + requests: + memory: 64Mi + cpu: 100m + limits: + memory: 256Mi + cpu: 200m + ports: - containerPort: 3000 - imagePullPolicy: Always + imagePullPolicy: IfNotPresent env: - name: NEXT_PUBLIC_REST_ENDPOINT value: "192.168.1.130:30003" - name: NEXT_PUBLIC_WS_ENDPOINT - value: "192.168.1.130:30002" - + value: "192.168.1.130:30005" --- apiVersion: v1 kind: Service diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml index 8d78cb6..0d705ee 100644 --- a/deploy/kubernetes/mqtt.yaml +++ b/deploy/kubernetes/mqtt.yaml @@ -9,7 +9,8 @@ spec: - protocol: TCP port: 1883 targetPort: 1883 - type: ClusterIP + nodePort: 30000 + type: NodePort --- apiVersion: apps/v1 kind: Deployment @@ -33,10 +34,10 @@ spec: resources: requests: memory: 64Mi - cpu: 0.1 + cpu: 100m limits: memory: 256Mi - cpu: 0.2 + cpu: 200m imagePullPolicy: IfNotPresent env: - name: MQTT_PORT diff --git a/deploy/kubernetes/ws.yaml b/deploy/kubernetes/ws.yaml index df38bc3..f53f334 100644 --- a/deploy/kubernetes/ws.yaml +++ b/deploy/kubernetes/ws.yaml @@ -31,13 +31,13 @@ spec: image: oktopusp/ws:latest ports: - containerPort: 8080 - #resources: - #requests: - #memory: 64Mi - #cpu: 0.1 - #limits: - #memory: 256Mi - #cpu: 0.2 + resources: + requests: + memory: 64Mi + cpu: 100m + limits: + memory: 256Mi + cpu: 200m imagePullPolicy: IfNotPresent env: - name: SERVER_PORT diff --git a/frontend/build/Dockerfile b/frontend/build/Dockerfile index 8a9893e..f7512cb 100644 --- a/frontend/build/Dockerfile +++ b/frontend/build/Dockerfile @@ -6,7 +6,7 @@ COPY ./ ./ RUN npm install -RUN NEXT_PUBLIC_REST_ENPOINT=REST_API_URL NEXT_PUBLIC_WS_ENPOINT=WS_URL npm run build +RUN NEXT_PUBLIC_REST_ENDPOINT=REST_API_URL NEXT_PUBLIC_WS_ENDPOINT=WS_URL npm run build RUN ls -la && echo "Listing directory contents done" From f39cd325b7046fdc0ec11040bf8f91f16222afef Mon Sep 17 00:00:00 2001 From: Roger Sacchelli Date: Tue, 16 Apr 2024 23:06:08 -0300 Subject: [PATCH 21/30] minor changes on Dockerfiles frontend --- backend/services/mtp/mqtt/build/Dockerfile | 2 +- frontend/build/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/services/mtp/mqtt/build/Dockerfile b/backend/services/mtp/mqtt/build/Dockerfile index 5ea1214..6a912db 100644 --- a/backend/services/mtp/mqtt/build/Dockerfile +++ b/backend/services/mtp/mqtt/build/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.22@sha256:82e07063a1ac3ee59e6f38b1222e32ce88469e4431ff6496cc40fb9a0fc18229 as builder WORKDIR /app -COPY ../ ./ +COPY ../ . RUN CGO_ENABLED=0 GOOS=linux go build -o mqtt cmd/mqtt/main.go FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed diff --git a/frontend/build/Dockerfile b/frontend/build/Dockerfile index f7512cb..a5d60f8 100644 --- a/frontend/build/Dockerfile +++ b/frontend/build/Dockerfile @@ -2,7 +2,7 @@ FROM node:16.20.2-alpine as builder WORKDIR /app -COPY ./ ./ +COPY ../ ./ RUN npm install From 558088d74b1d4b01be74bb7036574782404b9800 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 18 Apr 2024 17:00:48 -0300 Subject: [PATCH 22/30] feat: inital cwmp server --- backend/services/acs/.env | 0 backend/services/acs/.gitignore | 1 + backend/services/acs/README.md | 97 ++++ backend/services/acs/cmd/main.go | 19 + backend/services/acs/go.mod | 19 + backend/services/acs/go.sum | 20 + backend/services/acs/internal/auth/auth.go | 113 ++++ .../services/acs/internal/config/config.go | 111 ++++ backend/services/acs/internal/cwmp/cwmp.go | 541 ++++++++++++++++++ backend/services/acs/internal/nats/nats.go | 149 +++++ .../acs/internal/server/handler/handler.go | 215 +++++++ .../services/acs/internal/server/server.go | 23 + backend/services/mtp/adapter/.env.local | 2 - backend/services/mtp/adapter/.gitignore | 1 + deploy/compose/.env.mqtt | 3 +- deploy/compose/.env.ws | 1 + deploy/compose/docker-compose.yaml | 2 + 17 files changed, 1314 insertions(+), 3 deletions(-) create mode 100644 backend/services/acs/.env create mode 100644 backend/services/acs/.gitignore create mode 100644 backend/services/acs/README.md create mode 100644 backend/services/acs/cmd/main.go create mode 100644 backend/services/acs/go.mod create mode 100644 backend/services/acs/go.sum create mode 100644 backend/services/acs/internal/auth/auth.go create mode 100644 backend/services/acs/internal/config/config.go create mode 100644 backend/services/acs/internal/cwmp/cwmp.go create mode 100644 backend/services/acs/internal/nats/nats.go create mode 100644 backend/services/acs/internal/server/handler/handler.go create mode 100644 backend/services/acs/internal/server/server.go delete mode 100644 backend/services/mtp/adapter/.env.local create mode 100644 backend/services/mtp/adapter/.gitignore create mode 100644 deploy/compose/.env.ws diff --git a/backend/services/acs/.env b/backend/services/acs/.env new file mode 100644 index 0000000..e69de29 diff --git a/backend/services/acs/.gitignore b/backend/services/acs/.gitignore new file mode 100644 index 0000000..e45d6e3 --- /dev/null +++ b/backend/services/acs/.gitignore @@ -0,0 +1 @@ +*.local \ No newline at end of file diff --git a/backend/services/acs/README.md b/backend/services/acs/README.md new file mode 100644 index 0000000..d5cb5d6 --- /dev/null +++ b/backend/services/acs/README.md @@ -0,0 +1,97 @@ +# Moses ACS [![Build Status](https://travis-ci.org/lucacervasio/mosesacs.svg?branch=master)](https://travis-ci.org/lucacervasio/mosesacs) + +An ACS in Go for provisioning CPEs, suitable for test purposes or production deployment. + +## Getting started + +Install the package: + + go get oktopUSP/backend/services/acs + +Run daemon: + + mosesacs -d + +Connect to it and get a cli: + + mosesacs + +Congratulations, you've connected to the daemon via websocket. Now you can issue commands via CLI or browse the embedded webserver at http://localhost:9292/www + +## Compatibility on ARM + +Moses is built on purpose only with dependencies in pure GO. So it runs on ARM processors with no issues. We tested it on QNAP devices and Raspberry for remote control. + +## CLI commands + +### 1. `list`: list CPEs + + example: + +``` + moses@localhost:9292/> list + cpe list + CPE A54FD with OUI 006754 +``` + +### 2. `readMib SERIAL LEAF/SUBTREE`: read a specific leaf or a subtree + + example: + +``` + moses@localhost:9292/> readMib A54FD Device. + Received an Inform from [::1]:58582 (3191 bytes) with SerialNumber A54FD and EventCodes 6 CONNECTION REQUEST + InternetGatewayDevice.Time.NTPServer1 : pool.ntp.org + InternetGatewayDevice.Time.CurrentLocalTime : 2014-07-11T09:08:25 + InternetGatewayDevice.Time.LocalTimeZone : +00:00 + InternetGatewayDevice.Time.LocalTimeZoneName : Greenwich Mean Time : Dublin + InternetGatewayDevice.Time.DaylightSavingsUsed : 0 +``` + +### 3. `writeMib SERIAL LEAF VALUE`: issue a SetParameterValues and write a value into a leaf + + example: + +``` + moses@localhost:9292/> writeMib A54FD InternetGatewayDevice.Time.Enable false + Received an Inform from [::1]:58582 (3191 bytes) with SerialNumber A54FD and EventCodes 6 CONNECTION REQUEST +``` + +### 4. `GetParameterNames SERIAL LEAF/SUBTREE`: issue a GetParameterNames and get all leaves/objects at first level + + example: + +``` +moses@localhost:9292/> GetParameterNames A54FD InternetGatewayDevice. +Received an Inform from [::1]:55385 (3119 bytes) with SerialNumber A54FD and EventCodes 6 CONNECTION REQUEST +InternetGatewayDevice.LANDeviceNumberOfEntries : 0 +InternetGatewayDevice.WANDeviceNumberOfEntries : 0 +InternetGatewayDevice.DeviceInfo. : 0 +InternetGatewayDevice.ManagementServer. : 0 +InternetGatewayDevice.Time. : 0 +InternetGatewayDevice.Layer3Forwarding. : 0 +InternetGatewayDevice.LANDevice. : 0 +InternetGatewayDevice.WANDevice. : 0 +InternetGatewayDevice.X_00507F_InternetAcc. : 0 +InternetGatewayDevice.X_00507F_LAN. : 0 +InternetGatewayDevice.X_00507F_NAT. : 0 +InternetGatewayDevice.X_00507F_VLAN. : 0 +InternetGatewayDevice.X_00507F_Firewall. : 0 +InternetGatewayDevice.X_00507F_Applications. : 0 +InternetGatewayDevice.X_00507F_System. : 0 +InternetGatewayDevice.X_00507F_Status. : 0 +InternetGatewayDevice.X_00507F_Diagnostics. : 0 +``` + + + + +## Services exposed + +Moses exposes three services: + + - http://localhost:9292/acs is the endpoint for the CPEs to connect + - http://localhost:9292/www is the embedded webserver to control your CPEs + - ws://localhost:9292/ws is the websocket endpoint used by the cli to issue commands. Read about the API specification if you want to build a custom frontend which interacts with mosesacs daemon. + + diff --git a/backend/services/acs/cmd/main.go b/backend/services/acs/cmd/main.go new file mode 100644 index 0000000..3ab03b9 --- /dev/null +++ b/backend/services/acs/cmd/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "oktopUSP/backend/services/acs/internal/config" + "oktopUSP/backend/services/acs/internal/nats" + "oktopUSP/backend/services/acs/internal/server" + "oktopUSP/backend/services/acs/internal/server/handler" +) + +func main() { + + c := config.NewConfig() + + natsActions := nats.StartNatsClient(c.Nats) + + h := handler.NewHandler(natsActions.Publish, natsActions.Subscribe) + + server.Run(c.Acs, natsActions, h) +} diff --git a/backend/services/acs/go.mod b/backend/services/acs/go.mod new file mode 100644 index 0000000..5ba5c09 --- /dev/null +++ b/backend/services/acs/go.mod @@ -0,0 +1,19 @@ +module oktopUSP/backend/services/acs + +go 1.22.2 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/nats-io/nats.go v1.34.1 + github.com/oleiade/lane v1.0.1 + golang.org/x/net v0.24.0 +) + +require ( + github.com/klauspost/compress v1.17.2 // indirect + github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/backend/services/acs/go.sum b/backend/services/acs/go.sum new file mode 100644 index 0000000..f09390a --- /dev/null +++ b/backend/services/acs/go.sum @@ -0,0 +1,20 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4= +github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oleiade/lane v1.0.1 h1:hXofkn7GEOubzTwNpeL9MaNy8WxolCYb9cInAIeqShU= +github.com/oleiade/lane v1.0.1/go.mod h1:IyTkraa4maLfjq/GmHR+Dxb4kCMtEGeb+qmhlrQ5Mk4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/backend/services/acs/internal/auth/auth.go b/backend/services/acs/internal/auth/auth.go new file mode 100644 index 0000000..476936f --- /dev/null +++ b/backend/services/acs/internal/auth/auth.go @@ -0,0 +1,113 @@ +package auth + +import ( + "crypto/md5" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +type myjar struct { + jar map[string][]*http.Cookie +} + +func (p *myjar) SetCookies(u *url.URL, cookies []*http.Cookie) { + p.jar[u.Host] = cookies +} + +func (p *myjar) Cookies(u *url.URL) []*http.Cookie { + return p.jar[u.Host] +} + +func Auth(username string, password string, uri string) (bool, error) { + client := &http.Client{} + jar := &myjar{} + jar.jar = make(map[string][]*http.Cookie) + client.Jar = jar + var req *http.Request + var resp *http.Response + var err error + req, err = http.NewRequest("GET", uri, nil) + resp, err = client.Do(req) + if err != nil { + return false, err + } + if resp.StatusCode == 401 { + var authorization map[string]string = DigestAuthParams(resp) + realmHeader := authorization["realm"] + qopHeader := authorization["qop"] + nonceHeader := authorization["nonce"] + opaqueHeader := authorization["opaque"] + realm := realmHeader + // A1 + h := md5.New() + A1 := fmt.Sprintf("%s:%s:%s", username, realm, password) + io.WriteString(h, A1) + HA1 := fmt.Sprintf("%x", h.Sum(nil)) + + // A2 + h = md5.New() + A2 := fmt.Sprintf("GET:%s", "/auth") + io.WriteString(h, A2) + HA2 := fmt.Sprintf("%x", h.Sum(nil)) + + // response + cnonce := RandomKey() + response := H(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")) + + // now make header + AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s", opaque="%s", algorithm=MD5`, + username, realmHeader, nonceHeader, "/auth", cnonce, qopHeader, response, opaqueHeader) + req.Header.Set("Authorization", AuthHeader) + resp, err = client.Do(req) + } else { + return false, fmt.Errorf("response status code should have been 401, it was %v", resp.StatusCode) + } + return resp.StatusCode == 200, err +} + +/* +Parse Authorization header from the http.Request. Returns a map of +auth parameters or nil if the header is not a valid parsable Digest +auth header. +*/ +func DigestAuthParams(r *http.Response) map[string]string { + s := strings.SplitN(r.Header.Get("Www-Authenticate"), " ", 2) + if len(s) != 2 || s[0] != "Digest" { + return nil + } + + result := map[string]string{} + for _, kv := range strings.Split(s[1], ",") { + parts := strings.SplitN(kv, "=", 2) + if len(parts) != 2 { + continue + } + result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ") + } + return result +} +func RandomKey() string { + k := make([]byte, 12) + for bytes := 0; bytes < len(k); { + n, err := rand.Read(k[bytes:]) + if err != nil { + panic("rand.Read() failed") + } + bytes += n + } + return base64.StdEncoding.EncodeToString(k) +} + +/* +H function for MD5 algorithm (returns a lower-case hex MD5 digest) +*/ +func H(data string) string { + digest := md5.New() + digest.Write([]byte(data)) + return fmt.Sprintf("%x", digest.Sum(nil)) +} diff --git a/backend/services/acs/internal/config/config.go b/backend/services/acs/internal/config/config.go new file mode 100644 index 0000000..9ad7d24 --- /dev/null +++ b/backend/services/acs/internal/config/config.go @@ -0,0 +1,111 @@ +package config + +import ( + "context" + "flag" + "log" + "os" + "strconv" + + "github.com/joho/godotenv" +) + +const LOCAL_ENV = ".env.local" + +type Nats struct { + Url string + Name string + VerifyCertificates bool + Ctx context.Context +} + +type Acs struct { + Port string + Tls bool + TlsPort bool + NoTls bool + Username string + Password string + Route string +} + +type Config struct { + Acs Acs + Nats Nats +} + +func NewConfig() *Config { + + loadEnvVariables() + log.SetFlags(log.LstdFlags | log.Lshortfile) + + natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") + natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "adapter"), "name for nats client") + natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + acsPort := flag.String("acs_port", lookupEnvOrString("ACS_PORT", ":9292"), "port for acs server") + acsRoute := flag.String("acs_route", lookupEnvOrString("ACS_ROUTE", "/acs"), "route for acs server") + flHelp := flag.Bool("help", false, "Help") + + /* + App variables priority: + 1º - Flag through command line. + 2º - Env variables. + 3º - Default flag value. + */ + + flag.Parse() + + if *flHelp { + flag.Usage() + os.Exit(0) + } + + ctx := context.TODO() + + return &Config{ + Nats: Nats{ + Url: *natsUrl, + Name: *natsName, + VerifyCertificates: *natsVerifyCertificates, + Ctx: ctx, + }, + Acs: Acs{ + Port: *acsPort, + Route: *acsRoute, + }, + } +} + +func loadEnvVariables() { + err := godotenv.Load() + + if _, err := os.Stat(LOCAL_ENV); err == nil { + _ = godotenv.Overload(LOCAL_ENV) + log.Printf("Loaded variables from '%s'", LOCAL_ENV) + return + } + + if err != nil { + log.Println("Error to load environment variables:", err) + } else { + log.Println("Loaded variables from '.env'") + } +} + +func lookupEnvOrString(key string, defaultVal string) string { + if val, _ := os.LookupEnv(key); val != "" { + return val + } + return defaultVal +} + +func lookupEnvOrBool(key string, defaultVal bool) bool { + if val, _ := os.LookupEnv(key); val != "" { + v, err := strconv.ParseBool(val) + if err != nil { + log.Fatalf("LookupEnvOrBool[%s]: %v", key, err) + } + return v + } + return defaultVal +} diff --git a/backend/services/acs/internal/cwmp/cwmp.go b/backend/services/acs/internal/cwmp/cwmp.go new file mode 100644 index 0000000..4d69201 --- /dev/null +++ b/backend/services/acs/internal/cwmp/cwmp.go @@ -0,0 +1,541 @@ +package cwmp + +import ( + "crypto/rand" + "encoding/xml" + "fmt" + "strconv" + "strings" + "time" +) + +type SoapEnvelope struct { + XMLName xml.Name + Header SoapHeader + Body SoapBody +} + +type SoapHeader struct { + Id string `xml:"ID"` +} +type SoapBody struct { + CWMPMessage CWMPMessage `xml:",any"` +} + +type CWMPMessage struct { + XMLName xml.Name +} + +type EventStruct struct { + EventCode string + CommandKey string +} + +type ParameterValueStruct struct { + Name string + Value string +} + +type ParameterInfoStruct struct { + Name string + Writable string +} + +type SetParameterValues_ struct { + ParameterList []ParameterValueStruct `xml:"Body>SetParameterValues>ParameterList>ParameterValueStruct"` + ParameterKey string `xml:"Body>SetParameterValues>ParameterKey>string"` +} + +type GetParameterValues_ struct { + ParameterNames []string `xml:"Body>GetParameterValues>ParameterNames>string"` +} + +type GetParameterNames_ struct { + ParameterPath []string `xml:"Body>GetParameterNames>ParameterPath"` + NextLevel string `xml:"Body>GetParameterNames>NextLevel"` +} + +type GetParameterValuesResponse struct { + ParameterList []ParameterValueStruct `xml:"Body>GetParameterValuesResponse>ParameterList>ParameterValueStruct"` +} + +type GetParameterNamesResponse struct { + ParameterList []ParameterInfoStruct `xml:"Body>GetParameterNamesResponse>ParameterList>ParameterInfoStruct"` +} + +type CWMPInform struct { + DeviceId DeviceID `xml:"Body>Inform>DeviceId"` + Events []EventStruct `xml:"Body>Inform>Event>EventStruct"` + ParameterList []ParameterValueStruct `xml:"Body>Inform>ParameterList>ParameterValueStruct"` +} + +func (s *SoapEnvelope) KindOf() string { + return s.Body.CWMPMessage.XMLName.Local +} + +func (i *CWMPInform) GetEvents() string { + res := "" + for idx := range i.Events { + res += i.Events[idx].EventCode + } + + return res +} + +func (i *CWMPInform) GetConnectionRequest() string { + for idx := range i.ParameterList { + // valid condition for both tr98 and tr181 + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.ManagementServer.ConnectionRequestURL") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetSoftwareVersion() string { + for idx := range i.ParameterList { + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.SoftwareVersion") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetHardwareVersion() string { + for idx := range i.ParameterList { + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.HardwareVersion") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetDataModelType() string { + if strings.HasPrefix(i.ParameterList[0].Name, "InternetGatewayDevice") { + return "TR098" + } else if strings.HasPrefix(i.ParameterList[0].Name, "Device") { + return "TR181" + } + + return "" +} + +type DeviceID struct { + Manufacturer string + OUI string + SerialNumber string +} + +func InformResponse(mustUnderstand string) string { + mustUnderstandHeader := "" + if mustUnderstand != "" { + mustUnderstandHeader = `` + mustUnderstand + `` + } + + return ` + + ` + mustUnderstandHeader + ` + + + 1 + + +` +} + +func GetParameterValues(leaf string) string { + return ` + + + + + + ` + leaf + ` + + + +` +} + +func GetParameterMultiValues(leaves []string) string { + msg := ` + + + + + ` + + for idx := range leaves { + msg += `` + leaves[idx] + `` + + } + msg += ` + + +` + return msg +} + +func SetParameterValues(leaf string, value string) string { + return ` + + + + + + + ` + leaf + ` + ` + value + ` + + + LC1309` + randToken() + ` + + +` +} + +func randToken() string { + b := make([]byte, 8) + rand.Read(b) + return fmt.Sprintf("%x", b) +} + +func SetParameterMultiValues(data map[string]string) string { + msg := ` + + + + + ` + + for key, value := range data { + msg += ` + ` + key + ` + ` + value + ` + ` + } + + msg += ` + LC1309` + randToken() + ` + + +` + + return msg +} + +func GetParameterNames(leaf string, nextlevel int) string { + return ` + + + + + ` + leaf + ` + ` + strconv.Itoa(nextlevel) + ` + + +` +} + +func FactoryReset() string { + return ` + + + + + +` +} + +func Download(filetype, url, username, password, filesize string) string { + // 3 Vendor Configuration File + // 1 Firmware Upgrade Image + + return ` + + + + + MSDWK + ` + filetype + ` + ` + url + ` + ` + username + ` + ` + password + ` + ` + filesize + ` + + 0 + + + + +` +} + +func CancelTransfer() string { + return ` + + + + + + + +` +} + +type TimeWindowStruct struct { + WindowStart string + WindowEnd string + WindowMode string + UserMessage string + MaxRetries string +} + +func (window *TimeWindowStruct) String() string { + return ` +` + window.WindowStart + ` +` + window.WindowEnd + ` +` + window.WindowMode + ` +` + window.UserMessage + ` +` + window.MaxRetries + ` +` +} + +func ScheduleDownload(filetype, url, username, password, filesize string, windowslist []fmt.Stringer) string { + ret := ` + + + + + MSDWK + ` + filetype + ` + ` + url + ` + ` + username + ` + ` + password + ` + ` + filesize + ` + + ` + + for _, op := range windowslist { + ret += op.String() + } + + ret += ` + + +` + + return ret +} + +type InstallOpStruct struct { + Url string + Uuid string + Username string + Password string + ExecutionEnvironment string +} + +func (op *InstallOpStruct) String() string { + return ` + ` + op.Url + ` + ` + op.Uuid + ` + ` + op.Username + ` + ` + op.Password + ` + ` + op.ExecutionEnvironment + ` +` +} + +type UpdateOpStruct struct { + Uuid string + Version string + Url string + Username string + Password string +} + +func (op *UpdateOpStruct) String() string { + return ` +` + op.Uuid + ` +` + op.Version + ` +` + op.Url + ` +` + op.Username + ` +` + op.Password + ` +` +} + +type UninstallOpStruct struct { + Uuid string + Version string + ExecutionEnvironment string +} + +func (op *UninstallOpStruct) String() string { + return ` +` + op.Uuid + ` +` + op.Version + ` +` + op.ExecutionEnvironment + ` +` +} + +func ChangeDuState(ops []fmt.Stringer) string { + ret := ` + + + + +` + + for _, op := range ops { + ret += op.String() + } + + ret += ` + + + +` + + return ret +} + +// CPE side + +func Inform(serial string) string { + return `5058 + ADB Broadband +0013C8 +VV5522 +` + serial + ` + + +6 CONNECTION REQUEST + + + +1 +` + time.Now().Format(time.RFC3339) + ` +0 + +InternetGatewayDevice.ManagementServer.ConnectionRequestURL +http://104.199.175.27:7547/ConnectionRequest-` + serial + ` + +InternetGatewayDevice.ManagementServer.ParameterKey + + +InternetGatewayDevice.DeviceSummary +InternetGatewayDevice:1.2[](Baseline:1,EthernetLAN:1,WiFiLAN:1,ADSLWAN:1,EthernetWAN:1,QoS:1,QoSDynamicFlow:1,Bridging:1,Time:1,IPPing:1,TraceRoute:1,DeviceAssociation:1,UDPConnReq:1),VoiceService:1.0[1](TAEndpoint:1,SIPEndpoint:1) + +InternetGatewayDevice.DeviceInfo.HardwareVersion +` + serial + ` + +InternetGatewayDevice.DeviceInfo.ProvisioningCode +ABCD + +InternetGatewayDevice.DeviceInfo.SoftwareVersion +4.0.8.17785 + +InternetGatewayDevice.DeviceInfo.SpecVersion +1.0 + +InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress +12.0.0.10 + + + +` +} + +/* +func BuildGetParameterValuesResponse(serial string, leaves GetParameterValues_) string { + ret := ` + 3 + ` + + db, _ := sqlite3.Open("/tmp/cpe.db") + + n_leaves := 0 + var temp string + for _, leaf := range leaves.ParameterNames { + sql := "select key, value, tipo from params where key like '" + leaf + "%'" + for s, err := db.Query(sql); err == nil; err = s.Next() { + n_leaves++ + var key string + var value string + var tipo string + s.Scan(&key, &value, &tipo) + temp += ` + ` + key + ` + ` + value + ` + ` + } + } + + ret += `` + ret += temp + ret += `` + + return ret +} + +func BuildGetParameterNamesResponse(serial string, leaves GetParameterNames_) string { + ret := ` + 69 + ` + db, _ := sqlite3.Open("/tmp/cpe.db") + + obj := make(map[string]bool) + var temp string + for _, leaf := range leaves.ParameterPath { + fmt.Println(leaf) + sql := "select key, value, tipo from params where key like '" + leaf + "%'" + for s, err := db.Query(sql); err == nil; err = s.Next() { + var key string + var value string + var tipo string + s.Scan(&key, &value, &tipo) + var sp = strings.Split(strings.Split(key, leaf)[1], ".") + nextlevel, _ := strconv.Atoi(leaves.NextLevel) + if nextlevel == 0 { + root := leaf + obj[root] = true + for idx := range sp { + if idx == len(sp)-1 { + root = root + sp[idx] + } else { + root = root + sp[idx] + "." + } + obj[root] = true + } + } else { + if !obj[sp[0]] { + if len(sp) > 1 { + obj[leaf+sp[0]+"."] = true + } else { + obj[leaf+sp[0]] = true + } + + } + } + + } + } + + for o := range obj { + temp += ` + ` + o + ` + true + ` + } + + fmt.Println(len(obj)) + ret += `` + ret += temp + ret += `` + + return ret +} +*/ diff --git a/backend/services/acs/internal/nats/nats.go b/backend/services/acs/internal/nats/nats.go new file mode 100644 index 0000000..0b1e803 --- /dev/null +++ b/backend/services/acs/internal/nats/nats.go @@ -0,0 +1,149 @@ +package nats + +import ( + "context" + "errors" + "log" + "oktopUSP/backend/services/acs/internal/config" + "time" + + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" +) + +const ( + CWMP_STREAM_NAME = "cwmp" +) + +type NatsActions struct { + Publish func(string, []byte) error + Subscribe func(string, func(*nats.Msg)) error +} + +func StartNatsClient(c config.Nats) NatsActions { + + var ( + nc *nats.Conn + err error + ) + + opts := defineOptions(c) + + log.Printf("Connecting to NATS server %s", c.Url) + + for { + nc, err = nats.Connect(c.Url, opts...) + if err != nil { + time.Sleep(5 * time.Second) + continue + } + break + } + log.Printf("Successfully connected to NATS server %s", c.Url) + + js, err := jetstream.New(nc) + if err != nil { + log.Fatalf("Failed to create JetStream client: %v", err) + } + + streams := defineStreams() + err = createStreams(c.Ctx, js, streams) + if err != nil { + log.Fatalf("Failed to create Consumer: %v", err) + } + + consumers := defineConsumers() + err = createConsumers(c.Ctx, js, consumers) + if err != nil { + log.Fatalf("Failed to create Consumer: %v", err) + } + + return NatsActions{ + Publish: publisher(js), + Subscribe: subscriber(nc), + } +} + +func subscriber(nc *nats.Conn) func(string, func(*nats.Msg)) error { + return func(subject string, handler func(*nats.Msg)) error { + _, err := nc.Subscribe(subject, handler) + if err != nil { + log.Printf("error to subscribe to subject %s error: %q", subject, err) + } + return err + } +} + +func publisher(js jetstream.JetStream) func(string, []byte) error { + return func(subject string, payload []byte) error { + _, err := js.PublishAsync(subject, payload) + if err != nil { + log.Printf("error to send jetstream message: %q", err) + } + return err + } +} + +func createStreams(ctx context.Context, js jetstream.JetStream, streams []string) error { + for _, stream := range streams { + _, err := js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{ + Name: stream, + Description: "Stream for " + stream + " messages", + Subjects: []string{stream + ".>"}, + Retention: jetstream.InterestPolicy, + }) + if err != nil { + return errors.New(err.Error() + " | consumer:" + stream) + } + } + return nil +} + +func createConsumers(ctx context.Context, js jetstream.JetStream, consumers []string) error { + for _, consumer := range consumers { + _, err := js.CreateOrUpdateConsumer(ctx, consumer, jetstream.ConsumerConfig{ + Name: consumer, + Description: "Consumer for " + consumer + " messages", + AckPolicy: jetstream.AckExplicitPolicy, + Durable: consumer, + }) + if err != nil { + return err + } + } + return nil +} + +func defineStreams() []string { + return []string{ + CWMP_STREAM_NAME, + } +} + +func defineConsumers() []string { + return []string{ + CWMP_STREAM_NAME, + } +} + +func defineOptions(c config.Nats) []nats.Option { + var opts []nats.Option + + opts = append(opts, nats.Name(c.Name)) + opts = append(opts, nats.MaxReconnects(-1)) + opts = append(opts, nats.ReconnectWait(5*time.Second)) + opts = append(opts, nats.DisconnectErrHandler(func(nc *nats.Conn, err error) { + log.Printf("Got disconnected! Reason: %q\n", err) + })) + opts = append(opts, nats.ReconnectHandler(func(nc *nats.Conn) { + log.Printf("Got reconnected to %v!\n", nc.ConnectedUrl()) + })) + opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { + log.Printf("Connection closed. Reason: %q\n", nc.LastError()) + })) + if c.VerifyCertificates { + opts = append(opts, nats.RootCAs()) + } + + return opts +} diff --git a/backend/services/acs/internal/server/handler/handler.go b/backend/services/acs/internal/server/handler/handler.go new file mode 100644 index 0000000..e86e28a --- /dev/null +++ b/backend/services/acs/internal/server/handler/handler.go @@ -0,0 +1,215 @@ +package handler + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "log" + "net/http" + "oktopUSP/backend/services/acs/internal/cwmp" + "time" + + "github.com/nats-io/nats.go" + "github.com/oleiade/lane" + "golang.org/x/net/websocket" +) + +const Version = "1.0.0" + +type Request struct { + Id string + Websocket *websocket.Conn + CwmpMessage string + Callback func(msg *WsSendMessage) error +} + +type CPE struct { + SerialNumber string + Manufacturer string + OUI string + ConnectionRequestURL string + XmppId string + XmppUsername string + XmppPassword string + SoftwareVersion string + ExternalIPAddress string + State string + Queue *lane.Queue + Waiting *Request + HardwareVersion string + LastConnection time.Time + DataModel string + KeepConnectionOpen bool +} + +type Message struct { + SerialNumber string + Message string +} + +type WsMessage struct { + Cmd string +} + +type WsSendMessage struct { + MsgType string + Data json.RawMessage +} + +type MsgCPEs struct { + CPES map[string]CPE +} + +type Handler struct { + pub func(string, []byte) error + sub func(string, func(*nats.Msg)) error + cpes map[string]CPE +} + +func NewHandler(pub func(string, []byte) error, sub func(string, func(*nats.Msg)) error) *Handler { + return &Handler{ + pub: pub, + sub: sub, + cpes: make(map[string]CPE), + } +} + +func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { + + log.Printf("--> Connection from %s", r.RemoteAddr) + + defer r.Body.Close() + defer log.Printf("<-- Connection from %s closed", r.RemoteAddr) + + tmp, _ := ioutil.ReadAll(r.Body) + body := string(tmp) + len := len(body) + + log.Printf("body:\n %v", body) + + var envelope cwmp.SoapEnvelope + xml.Unmarshal(tmp, &envelope) + + messageType := envelope.Body.CWMPMessage.XMLName.Local + + var cpe *CPE + + w.Header().Set("Server", "Oktopus "+Version) + + if messageType != "Inform" { + if cookie, err := r.Cookie("oktopus"); err == nil { + if _, exists := h.cpes[cookie.Value]; !exists { + log.Printf("CPE with serial number %s not found", cookie.Value) + } + } else { + fmt.Println("cookie 'oktopus' missing") + w.WriteHeader(401) + return + } + } + + if messageType == "Inform" { + var Inform cwmp.CWMPInform + xml.Unmarshal(tmp, &Inform) + + var addr string + if r.Header.Get("X-Real-Ip") != "" { + addr = r.Header.Get("X-Real-Ip") + } else { + addr = r.RemoteAddr + } + + sn := Inform.DeviceId.SerialNumber + + if _, exists := h.cpes[sn]; !exists { + fmt.Println("New device: " + sn) + h.cpes[sn] = CPE{ + SerialNumber: sn, + LastConnection: time.Now().UTC(), + SoftwareVersion: Inform.GetSoftwareVersion(), + HardwareVersion: Inform.GetHardwareVersion(), + ExternalIPAddress: addr, + ConnectionRequestURL: Inform.GetConnectionRequest(), + OUI: Inform.DeviceId.OUI, + Queue: lane.NewQueue(), + DataModel: Inform.GetDataModelType(), + KeepConnectionOpen: false} + } + obj := h.cpes[sn] + cpe := &obj + cpe.LastConnection = time.Now().UTC() + + log.Printf("Received an Inform from %s (%d bytes) with SerialNumber %s and EventCodes %s", addr, len, sn, Inform.GetEvents()) + + expiration := time.Now().AddDate(0, 0, 1) + + cookie := http.Cookie{Name: "oktopus", Value: sn, Expires: expiration} + http.SetCookie(w, &cookie) + } else if messageType == "TransferComplete" { + + } else if messageType == "GetRPC" { + + } else { + if len == 0 { + log.Printf("Got Empty Post") + } + + if cpe.Waiting != nil { + var e cwmp.SoapEnvelope + xml.Unmarshal([]byte(body), &e) + + if e.KindOf() == "GetParameterNamesResponse" { + var envelope cwmp.GetParameterNamesResponse + xml.Unmarshal([]byte(body), &envelope) + + msg := new(WsSendMessage) + msg.MsgType = "GetParameterNamesResponse" + msg.Data, _ = json.Marshal(envelope) + + cpe.Waiting.Callback(msg) + // if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { + // fmt.Println("error while sending back answer:", err) + // } + + } else if e.KindOf() == "GetParameterValuesResponse" { + var envelope cwmp.GetParameterValuesResponse + xml.Unmarshal([]byte(body), &envelope) + + msg := new(WsSendMessage) + msg.MsgType = "GetParameterValuesResponse" + msg.Data, _ = json.Marshal(envelope) + + cpe.Waiting.Callback(msg) + // if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { + // fmt.Println("error while sending back answer:", err) + // } + + } else { + msg := new(WsMessage) + msg.Cmd = body + + if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { + fmt.Println("error while sending back answer:", err) + } + + } + + cpe.Waiting = nil + } + + // Got Empty Post or a Response. Now check for any event to send, otherwise 204 + if cpe.Queue.Size() > 0 { + req := cpe.Queue.Dequeue().(Request) + // fmt.Println("sending "+req.CwmpMessage) + fmt.Fprintf(w, req.CwmpMessage) + cpe.Waiting = &req + } else { + if cpe.KeepConnectionOpen { + fmt.Println("I'm keeping connection open") + } else { + w.WriteHeader(204) + } + } + } +} diff --git a/backend/services/acs/internal/server/server.go b/backend/services/acs/internal/server/server.go new file mode 100644 index 0000000..4a2bc6b --- /dev/null +++ b/backend/services/acs/internal/server/server.go @@ -0,0 +1,23 @@ +package server + +import ( + "log" + "net/http" + "oktopUSP/backend/services/acs/internal/config" + "oktopUSP/backend/services/acs/internal/nats" + "oktopUSP/backend/services/acs/internal/server/handler" + "os" +) + +func Run(c config.Acs, natsActions nats.NatsActions, h *handler.Handler) { + + http.HandleFunc(c.Route, h.CwmpHandler) + + log.Printf("ACS running at %s%s", c.Port, c.Route) + + err := http.ListenAndServe(c.Port, nil) + if err != nil { + log.Fatal(err) + os.Exit(1) + } +} diff --git a/backend/services/mtp/adapter/.env.local b/backend/services/mtp/adapter/.env.local deleted file mode 100644 index c9b245e..0000000 --- a/backend/services/mtp/adapter/.env.local +++ /dev/null @@ -1,2 +0,0 @@ -MONGO_URI="mongodb://localhost:27017" -CONTROLLER_PASSWORD="test123" diff --git a/backend/services/mtp/adapter/.gitignore b/backend/services/mtp/adapter/.gitignore new file mode 100644 index 0000000..e45d6e3 --- /dev/null +++ b/backend/services/mtp/adapter/.gitignore @@ -0,0 +1 @@ +*.local \ No newline at end of file diff --git a/deploy/compose/.env.mqtt b/deploy/compose/.env.mqtt index 2939a97..e0c2d35 100644 --- a/deploy/compose/.env.mqtt +++ b/deploy/compose/.env.mqtt @@ -1,2 +1,3 @@ REDIS_ENABLE=false -REDIS_ADDR=redis_usp:6379 \ No newline at end of file +REDIS_ADDR=redis_usp:6379 +NATS_URL=nats://msg_broker:4222 \ No newline at end of file diff --git a/deploy/compose/.env.ws b/deploy/compose/.env.ws new file mode 100644 index 0000000..9af7128 --- /dev/null +++ b/deploy/compose/.env.ws @@ -0,0 +1 @@ +NATS_URL=nats://msg_broker:4222 \ No newline at end of file diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index b2433ac..acf57fb 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -66,6 +66,8 @@ services: container_name: websockets ports: - 8080:8080 + env_file: + - .env.ws networks: usp_network: ipv4_address: 172.16.235.7 From 12f5976c296f5b7c94999739eabb8ccd45261987 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 18 Apr 2024 18:05:07 -0300 Subject: [PATCH 23/30] build: remove github actions since we already use circlei --- .github/workflows/frontend.yaml | 39 --------------------------------- .github/workflows/mqtt.yaml | 39 --------------------------------- 2 files changed, 78 deletions(-) delete mode 100644 .github/workflows/frontend.yaml delete mode 100644 .github/workflows/mqtt.yaml diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml deleted file mode 100644 index 29831d2..0000000 --- a/.github/workflows/frontend.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Oktopus Frontend - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build-frontend: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - - name: Build Docker image - run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/frontend:latest -f frontend/build/Dockerfile frontend - - - name: Push Docker image to Docker Hub - run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest - diff --git a/.github/workflows/mqtt.yaml b/.github/workflows/mqtt.yaml deleted file mode 100644 index a3d5d91..0000000 --- a/.github/workflows/mqtt.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Oktopus MQTT Broker - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build-mqtt: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - - name: Build Docker image - run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest -f backend/services/mtp/mqtt/build/Dockerfile backend/services/mtp/mqtt - - - name: Push Docker image to Docker Hub - run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/mqtt:latest - From 63db492e4bea4b624a6206e713a1c875aca2333c Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 18 Apr 2024 18:56:46 -0300 Subject: [PATCH 24/30] feat: cwmp save basic device data --- backend/services/acs/internal/cwmp/cwmp.go | 1 + .../acs/internal/server/handler/handler.go | 6 +- .../services/mtp/adapter/cmd/adapter/main.go | 8 +- .../mtp/adapter/internal/cwmp/cwmp.go | 500 ++++++++++++++++++ .../mtp/adapter/internal/db/device.go | 48 +- .../internal/events/cwmp_handler/handler.go | 28 + .../internal/events/cwmp_handler/info.go | 41 ++ .../mtp/adapter/internal/events/events.go | 60 ++- .../adapter/internal/events/handler/status.go | 72 --- .../{handler => usp_handler}/handler.go | 2 +- .../events/{handler => usp_handler}/info.go | 3 +- .../internal/events/usp_handler/status.go | 38 ++ .../mtp/adapter/internal/nats/nats.go | 3 + 13 files changed, 705 insertions(+), 105 deletions(-) create mode 100644 backend/services/mtp/adapter/internal/cwmp/cwmp.go create mode 100644 backend/services/mtp/adapter/internal/events/cwmp_handler/handler.go create mode 100644 backend/services/mtp/adapter/internal/events/cwmp_handler/info.go delete mode 100644 backend/services/mtp/adapter/internal/events/handler/status.go rename backend/services/mtp/adapter/internal/events/{handler => usp_handler}/handler.go (95%) rename backend/services/mtp/adapter/internal/events/{handler => usp_handler}/info.go (97%) create mode 100644 backend/services/mtp/adapter/internal/events/usp_handler/status.go diff --git a/backend/services/acs/internal/cwmp/cwmp.go b/backend/services/acs/internal/cwmp/cwmp.go index 4d69201..a0aaa06 100644 --- a/backend/services/acs/internal/cwmp/cwmp.go +++ b/backend/services/acs/internal/cwmp/cwmp.go @@ -127,6 +127,7 @@ type DeviceID struct { Manufacturer string OUI string SerialNumber string + ProductClass string } func InformResponse(mustUnderstand string) string { diff --git a/backend/services/acs/internal/server/handler/handler.go b/backend/services/acs/internal/server/handler/handler.go index e86e28a..3f040b2 100644 --- a/backend/services/acs/internal/server/handler/handler.go +++ b/backend/services/acs/internal/server/handler/handler.go @@ -86,7 +86,7 @@ func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { body := string(tmp) len := len(body) - log.Printf("body:\n %v", body) + //log.Printf("body:\n %v", body) var envelope cwmp.SoapEnvelope xml.Unmarshal(tmp, &envelope) @@ -134,7 +134,9 @@ func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { OUI: Inform.DeviceId.OUI, Queue: lane.NewQueue(), DataModel: Inform.GetDataModelType(), - KeepConnectionOpen: false} + KeepConnectionOpen: false, + } + h.pub("cwmp.v1."+sn+".info", tmp) //TODO: send right info } obj := h.cpes[sn] cpe := &obj diff --git a/backend/services/mtp/adapter/cmd/adapter/main.go b/backend/services/mtp/adapter/cmd/adapter/main.go index cd32564..02c5192 100644 --- a/backend/services/mtp/adapter/cmd/adapter/main.go +++ b/backend/services/mtp/adapter/cmd/adapter/main.go @@ -9,7 +9,8 @@ import ( "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/config" "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events" - "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/handler" + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/cwmp_handler" + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/usp_handler" "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/nats" "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/reqs" ) @@ -24,9 +25,10 @@ func main() { db := db.NewDatabase(c.Mongo.Ctx, c.Mongo.Uri) - handler := handler.NewHandler(nc, js, db, c.Controller.ControllerId) + usp_handler := usp_handler.NewHandler(nc, js, db, c.Controller.ControllerId) + cwmp_handler := cwmp_handler.NewHandler(nc, js, db, c.Controller.ControllerId) - events.StartEventsListener(c.Nats.Ctx, js, handler) + events.StartEventsListener(c.Nats.Ctx, js, usp_handler, cwmp_handler) reqs.StartRequestsListener(c.Nats.Ctx, nc, db) diff --git a/backend/services/mtp/adapter/internal/cwmp/cwmp.go b/backend/services/mtp/adapter/internal/cwmp/cwmp.go new file mode 100644 index 0000000..6a8161e --- /dev/null +++ b/backend/services/mtp/adapter/internal/cwmp/cwmp.go @@ -0,0 +1,500 @@ +package cwmp + +import ( + "crypto/rand" + "encoding/xml" + "fmt" + "strconv" + "strings" +) + +type MsgType string + +const ( + INFORM = "Inform" +) + +type SoapEnvelope struct { + XMLName xml.Name + Header SoapHeader + Body SoapBody +} + +type SoapHeader struct { + Id string `xml:"ID"` +} +type SoapBody struct { + CWMPMessage CWMPMessage `xml:",any"` +} + +type CWMPMessage struct { + XMLName xml.Name +} + +type EventStruct struct { + EventCode string + CommandKey string +} + +type ParameterValueStruct struct { + Name string + Value string +} + +type ParameterInfoStruct struct { + Name string + Writable string +} + +type SetParameterValues_ struct { + ParameterList []ParameterValueStruct `xml:"Body>SetParameterValues>ParameterList>ParameterValueStruct"` + ParameterKey string `xml:"Body>SetParameterValues>ParameterKey>string"` +} + +type GetParameterValues_ struct { + ParameterNames []string `xml:"Body>GetParameterValues>ParameterNames>string"` +} + +type GetParameterNames_ struct { + ParameterPath []string `xml:"Body>GetParameterNames>ParameterPath"` + NextLevel string `xml:"Body>GetParameterNames>NextLevel"` +} + +type GetParameterValuesResponse struct { + ParameterList []ParameterValueStruct `xml:"Body>GetParameterValuesResponse>ParameterList>ParameterValueStruct"` +} + +type GetParameterNamesResponse struct { + ParameterList []ParameterInfoStruct `xml:"Body>GetParameterNamesResponse>ParameterList>ParameterInfoStruct"` +} + +type CWMPInform struct { + DeviceId DeviceID `xml:"Body>Inform>DeviceId"` + Events []EventStruct `xml:"Body>Inform>Event>EventStruct"` + ParameterList []ParameterValueStruct `xml:"Body>Inform>ParameterList>ParameterValueStruct"` +} + +func (s *SoapEnvelope) KindOf() string { + return s.Body.CWMPMessage.XMLName.Local +} + +func (i *CWMPInform) GetEvents() string { + res := "" + for idx := range i.Events { + res += i.Events[idx].EventCode + } + + return res +} + +func (i *CWMPInform) GetConnectionRequest() string { + for idx := range i.ParameterList { + // valid condition for both tr98 and tr181 + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.ManagementServer.ConnectionRequestURL") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetSoftwareVersion() string { + for idx := range i.ParameterList { + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.SoftwareVersion") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetHardwareVersion() string { + for idx := range i.ParameterList { + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.HardwareVersion") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetDataModelType() string { + if strings.HasPrefix(i.ParameterList[0].Name, "InternetGatewayDevice") { + return "TR098" + } else if strings.HasPrefix(i.ParameterList[0].Name, "Device") { + return "TR181" + } + + return "" +} + +type DeviceID struct { + Manufacturer string + OUI string + SerialNumber string + ProductClass string +} + +func InformResponse(mustUnderstand string) string { + mustUnderstandHeader := "" + if mustUnderstand != "" { + mustUnderstandHeader = `` + mustUnderstand + `` + } + + return ` + + ` + mustUnderstandHeader + ` + + + 1 + + +` +} + +func GetParameterValues(leaf string) string { + return ` + + + + + + ` + leaf + ` + + + +` +} + +func GetParameterMultiValues(leaves []string) string { + msg := ` + + + + + ` + + for idx := range leaves { + msg += `` + leaves[idx] + `` + + } + msg += ` + + +` + return msg +} + +func SetParameterValues(leaf string, value string) string { + return ` + + + + + + + ` + leaf + ` + ` + value + ` + + + LC1309` + randToken() + ` + + +` +} + +func randToken() string { + b := make([]byte, 8) + rand.Read(b) + return fmt.Sprintf("%x", b) +} + +func SetParameterMultiValues(data map[string]string) string { + msg := ` + + + + + ` + + for key, value := range data { + msg += ` + ` + key + ` + ` + value + ` + ` + } + + msg += ` + LC1309` + randToken() + ` + + +` + + return msg +} + +func GetParameterNames(leaf string, nextlevel int) string { + return ` + + + + + ` + leaf + ` + ` + strconv.Itoa(nextlevel) + ` + + +` +} + +func FactoryReset() string { + return ` + + + + + +` +} + +func Download(filetype, url, username, password, filesize string) string { + // 3 Vendor Configuration File + // 1 Firmware Upgrade Image + + return ` + + + + + MSDWK + ` + filetype + ` + ` + url + ` + ` + username + ` + ` + password + ` + ` + filesize + ` + + 0 + + + + +` +} + +func CancelTransfer() string { + return ` + + + + + + + +` +} + +type TimeWindowStruct struct { + WindowStart string + WindowEnd string + WindowMode string + UserMessage string + MaxRetries string +} + +func (window *TimeWindowStruct) String() string { + return ` +` + window.WindowStart + ` +` + window.WindowEnd + ` +` + window.WindowMode + ` +` + window.UserMessage + ` +` + window.MaxRetries + ` +` +} + +func ScheduleDownload(filetype, url, username, password, filesize string, windowslist []fmt.Stringer) string { + ret := ` + + + + + MSDWK + ` + filetype + ` + ` + url + ` + ` + username + ` + ` + password + ` + ` + filesize + ` + + ` + + for _, op := range windowslist { + ret += op.String() + } + + ret += ` + + +` + + return ret +} + +type InstallOpStruct struct { + Url string + Uuid string + Username string + Password string + ExecutionEnvironment string +} + +func (op *InstallOpStruct) String() string { + return ` + ` + op.Url + ` + ` + op.Uuid + ` + ` + op.Username + ` + ` + op.Password + ` + ` + op.ExecutionEnvironment + ` +` +} + +type UpdateOpStruct struct { + Uuid string + Version string + Url string + Username string + Password string +} + +func (op *UpdateOpStruct) String() string { + return ` +` + op.Uuid + ` +` + op.Version + ` +` + op.Url + ` +` + op.Username + ` +` + op.Password + ` +` +} + +type UninstallOpStruct struct { + Uuid string + Version string + ExecutionEnvironment string +} + +func (op *UninstallOpStruct) String() string { + return ` +` + op.Uuid + ` +` + op.Version + ` +` + op.ExecutionEnvironment + ` +` +} + +func ChangeDuState(ops []fmt.Stringer) string { + ret := ` + + + + +` + + for _, op := range ops { + ret += op.String() + } + + ret += ` + + + +` + + return ret +} + +/* +func BuildGetParameterValuesResponse(serial string, leaves GetParameterValues_) string { + ret := ` + 3 + ` + + db, _ := sqlite3.Open("/tmp/cpe.db") + + n_leaves := 0 + var temp string + for _, leaf := range leaves.ParameterNames { + sql := "select key, value, tipo from params where key like '" + leaf + "%'" + for s, err := db.Query(sql); err == nil; err = s.Next() { + n_leaves++ + var key string + var value string + var tipo string + s.Scan(&key, &value, &tipo) + temp += ` + ` + key + ` + ` + value + ` + ` + } + } + + ret += `` + ret += temp + ret += `` + + return ret +} + +func BuildGetParameterNamesResponse(serial string, leaves GetParameterNames_) string { + ret := ` + 69 + ` + db, _ := sqlite3.Open("/tmp/cpe.db") + + obj := make(map[string]bool) + var temp string + for _, leaf := range leaves.ParameterPath { + fmt.Println(leaf) + sql := "select key, value, tipo from params where key like '" + leaf + "%'" + for s, err := db.Query(sql); err == nil; err = s.Next() { + var key string + var value string + var tipo string + s.Scan(&key, &value, &tipo) + var sp = strings.Split(strings.Split(key, leaf)[1], ".") + nextlevel, _ := strconv.Atoi(leaves.NextLevel) + if nextlevel == 0 { + root := leaf + obj[root] = true + for idx := range sp { + if idx == len(sp)-1 { + root = root + sp[idx] + } else { + root = root + sp[idx] + "." + } + obj[root] = true + } + } else { + if !obj[sp[0]] { + if len(sp) > 1 { + obj[leaf+sp[0]+"."] = true + } else { + obj[leaf+sp[0]] = true + } + + } + } + + } + } + + for o := range obj { + temp += ` + ` + o + ` + true + ` + } + + fmt.Println(len(obj)) + ret += `` + ret += temp + ret += `` + + return ret +} +*/ diff --git a/backend/services/mtp/adapter/internal/db/device.go b/backend/services/mtp/adapter/internal/db/device.go index 14dcd9d..0097c63 100644 --- a/backend/services/mtp/adapter/internal/db/device.go +++ b/backend/services/mtp/adapter/internal/db/device.go @@ -25,6 +25,15 @@ const ( Online ) +type ManagementProtocol uint8 + +const ( + UNKNOWN ManagementProtocol = iota + USP + CWMP + MATTER +) + type Device struct { SN string Model string @@ -36,6 +45,7 @@ type Device struct { Mqtt Status Stomp Status Websockets Status + Protocol ManagementProtocol } func (d *Database) CreateDevice(device Device) error { @@ -45,32 +55,34 @@ func (d *Database) CreateDevice(device Device) error { d.m.Lock() defer d.m.Unlock() - /* ------------------ Do not overwrite status of other mtp ------------------ */ - err := d.devices.FindOne(d.ctx, bson.D{{"sn", device.SN}}, nil).Decode(&deviceExistent) - if err == nil { - if deviceExistent.Mqtt == Online { - device.Mqtt = Online - } - if deviceExistent.Stomp == Online { - device.Stomp = Online - } - if deviceExistent.Websockets == Online { - device.Websockets = Online - } - } else { - if err != nil && err != mongo.ErrNoDocuments { - log.Println(err) - return err + if device.Protocol == USP { + /* ------------------ Do not overwrite status of other mtp ------------------ */ + err := d.devices.FindOne(d.ctx, bson.D{{"sn", device.SN}}, nil).Decode(&deviceExistent) + if err == nil { + if deviceExistent.Mqtt == Online { + device.Mqtt = Online + } + if deviceExistent.Stomp == Online { + device.Stomp = Online + } + if deviceExistent.Websockets == Online { + device.Websockets = Online + } + } else { + if err != mongo.ErrNoDocuments { + log.Println(err) + return err + } } + /* -------------------------------------------------------------------------- */ } - /* -------------------------------------------------------------------------- */ callback := func(sessCtx mongo.SessionContext) (interface{}, error) { // Important: You must pass sessCtx as the Context parameter to the operations for them to be executed in the // transaction. opts := options.FindOneAndReplace().SetUpsert(true) - err = d.devices.FindOneAndReplace(d.ctx, bson.D{{"sn", device.SN}}, device, opts).Decode(&result) + err := d.devices.FindOneAndReplace(d.ctx, bson.D{{"sn", device.SN}}, device, opts).Decode(&result) if err != nil { if err == mongo.ErrNoDocuments { log.Printf("New device %s added to database", device.SN) diff --git a/backend/services/mtp/adapter/internal/events/cwmp_handler/handler.go b/backend/services/mtp/adapter/internal/events/cwmp_handler/handler.go new file mode 100644 index 0000000..5f03861 --- /dev/null +++ b/backend/services/mtp/adapter/internal/events/cwmp_handler/handler.go @@ -0,0 +1,28 @@ +package cwmp_handler + +import ( + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" +) + +const ( + OFFLINE = iota + ONLINE +) + +type Handler struct { + nc *nats.Conn + js jetstream.JetStream + db db.Database + cid string +} + +func NewHandler(nc *nats.Conn, js jetstream.JetStream, d db.Database, cid string) Handler { + return Handler{ + nc: nc, + js: js, + db: d, + cid: cid, + } +} diff --git a/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go b/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go new file mode 100644 index 0000000..e0f5174 --- /dev/null +++ b/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go @@ -0,0 +1,41 @@ +package cwmp_handler + +import ( + "encoding/xml" + "log" + + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/cwmp" + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" +) + +func (h *Handler) HandleDeviceInfo(device string, data []byte, ack func()) { + defer ack() + log.Printf("Device %s info", device) + deviceInfo := parseDeviceInfoMsg(data) + err := h.db.CreateDevice(deviceInfo) + if err != nil { + log.Printf("Failed to create device: %v", err) + } +} + +func parseDeviceInfoMsg(data []byte) db.Device { + + var inform cwmp.CWMPInform + err := xml.Unmarshal(data, &inform) + if err != nil { + log.Println("Error unmarshalling xml:", err) + } + + var device db.Device + + device.Vendor = inform.DeviceId.Manufacturer + device.Model = "" + device.Version = inform.GetSoftwareVersion() + device.ProductClass = inform.DeviceId.ProductClass + device.SN = inform.DeviceId.SerialNumber + + device.Status = db.Online + device.Protocol = db.CWMP + + return device +} diff --git a/backend/services/mtp/adapter/internal/events/events.go b/backend/services/mtp/adapter/internal/events/events.go index f5b5179..207337a 100644 --- a/backend/services/mtp/adapter/internal/events/events.go +++ b/backend/services/mtp/adapter/internal/events/events.go @@ -5,16 +5,17 @@ import ( "log" "strings" - "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/handler" + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/cwmp_handler" + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/events/usp_handler" "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/nats" "github.com/nats-io/nats.go/jetstream" ) -func StartEventsListener(ctx context.Context, js jetstream.JetStream, h handler.Handler) { +func StartEventsListener(ctx context.Context, js jetstream.JetStream, uspHandler usp_handler.Handler, cwmpHandler cwmp_handler.Handler) { log.Println("Listening for nats events") - events := []string{ + uspEvents := []string{ nats.MQTT_STREAM_NAME, nats.WS_STREAM_NAME, nats.STOMP_STREAM_NAME, @@ -22,8 +23,8 @@ func StartEventsListener(ctx context.Context, js jetstream.JetStream, h handler. nats.OPC_STREAM_NAME, } - for _, event := range events { - go func() { + for _, uspEvent := range uspEvents { + go func(event string) { consumer, err := js.Consumer(ctx, event, event) if err != nil { log.Fatalf("Failed to get consumer: %v", err) @@ -50,14 +51,57 @@ func StartEventsListener(ctx context.Context, js jetstream.JetStream, h handler. switch msgType { case "status": - h.HandleDeviceStatus(device, msg.Subject(), data, event, func() { msg.Ack() }) + uspHandler.HandleDeviceStatus(device, msg.Subject(), data, event, func() { msg.Ack() }) case "info": - h.HandleDeviceInfo(device, msg.Subject(), data, event, func() { msg.Ack() }) + uspHandler.HandleDeviceInfo(device, msg.Subject(), data, event, func() { msg.Ack() }) default: log.Printf("Unknown message type received, subject: %s", msg.Subject()) msg.Ack() } } - }() + }(uspEvent) + } + + cwmpEvents := []string{ + nats.CWMP_STREAM_NAME, + } + + for _, cwmpEvent := range cwmpEvents { + go func(event string) { + consumer, err := js.Consumer(ctx, event, event) + if err != nil { + log.Fatalf("Failed to get consumer: %v", err) + } + messages, err := consumer.Messages() + if err != nil { + log.Fatalf("Failed to get consumer messages: %v", err) + } + defer messages.Stop() + for { + msg, err := messages.Next() + if err != nil { + log.Println("Error to get next message:", err) + continue + } + + data := msg.Data() + + log.Printf("Received message, subject: %s", msg.Subject()) + + subject := strings.Split(msg.Subject(), ".") + msgType := subject[len(subject)-1] + device := subject[len(subject)-2] + + switch msgType { + case "status": + uspHandler.HandleDeviceStatus(device, msg.Subject(), data, event, func() { msg.Ack() }) + case "info": + cwmpHandler.HandleDeviceInfo(device, data, func() { msg.Ack() }) + default: + log.Printf("Unknown message type received, subject: %s", msg.Subject()) + msg.Ack() + } + } + }(cwmpEvent) } } diff --git a/backend/services/mtp/adapter/internal/events/handler/status.go b/backend/services/mtp/adapter/internal/events/handler/status.go deleted file mode 100644 index 8fefd2e..0000000 --- a/backend/services/mtp/adapter/internal/events/handler/status.go +++ /dev/null @@ -1,72 +0,0 @@ -package handler - -import ( - "log" - "strconv" - - "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" - "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/usp" - "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/usp/usp_msg" - "google.golang.org/protobuf/proto" -) - -func (h *Handler) HandleDeviceStatus(device, subject string, data []byte, mtp string, ack func()) { - defer ack() - payload, err := strconv.Atoi(string(data)) - if err != nil { - log.Printf("Status subject payload message error %q", err) - } - - switch payload { - case ONLINE: - h.deviceOnline(device, mtp) - case OFFLINE: - h.deviceOffline(device, mtp) - default: - ignoreMsg(subject, "status", data) - } -} - -func (h *Handler) deviceOnline(device, mtp string) { - - log.Printf("Device %s is online", device) - - msg := usp.NewGetMsg(usp_msg.Get{ - ParamPaths: []string{ - "Device.DeviceInfo.Manufacturer", - "Device.DeviceInfo.ModelName", - "Device.DeviceInfo.SoftwareVersion", - "Device.DeviceInfo.SerialNumber", - "Device.DeviceInfo.ProductClass", - }, - MaxDepth: 1, - }) - - payload, _ := proto.Marshal(&msg) - record := usp.NewUspRecord(payload, device, h.cid) - - tr369Message, err := proto.Marshal(&record) - if err != nil { - log.Fatalln("Failed to encode tr369 record:", err) - } - - err = h.nc.Publish(mtp+"-adapter.usp.v1."+device+".info", tr369Message) - if err != nil { - log.Printf("Failed to publish online device message: %v", err) - } -} - -func (h *Handler) deviceOffline(device, mtp string) { - log.Printf("Device %s is offline", device) - - mtpLayer := getMtp(mtp) - - err := h.db.UpdateStatus(device, db.Offline, mtpLayer) - if err != nil { - log.Fatal(err) - } -} - -func ignoreMsg(subject, ctx string, data []byte) { - log.Printf("Unknown message of %s received, subject: %s, payload: %s. Ignored...", ctx, subject, string(data)) -} diff --git a/backend/services/mtp/adapter/internal/events/handler/handler.go b/backend/services/mtp/adapter/internal/events/usp_handler/handler.go similarity index 95% rename from backend/services/mtp/adapter/internal/events/handler/handler.go rename to backend/services/mtp/adapter/internal/events/usp_handler/handler.go index 439e3ed..ef0263a 100644 --- a/backend/services/mtp/adapter/internal/events/handler/handler.go +++ b/backend/services/mtp/adapter/internal/events/usp_handler/handler.go @@ -1,4 +1,4 @@ -package handler +package usp_handler import ( "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" diff --git a/backend/services/mtp/adapter/internal/events/handler/info.go b/backend/services/mtp/adapter/internal/events/usp_handler/info.go similarity index 97% rename from backend/services/mtp/adapter/internal/events/handler/info.go rename to backend/services/mtp/adapter/internal/events/usp_handler/info.go index 32846ce..6024f49 100644 --- a/backend/services/mtp/adapter/internal/events/handler/info.go +++ b/backend/services/mtp/adapter/internal/events/usp_handler/info.go @@ -1,4 +1,4 @@ -package handler +package usp_handler import ( "log" @@ -64,6 +64,7 @@ func parseDeviceInfoMsg(sn, subject string, data []byte, mtp db.MTP) db.Device { } device.Status = db.Online + device.Protocol = db.USP return device } diff --git a/backend/services/mtp/adapter/internal/events/usp_handler/status.go b/backend/services/mtp/adapter/internal/events/usp_handler/status.go new file mode 100644 index 0000000..b9e2c40 --- /dev/null +++ b/backend/services/mtp/adapter/internal/events/usp_handler/status.go @@ -0,0 +1,38 @@ +package usp_handler + +import ( + "log" + "strconv" + + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" +) + +func (h *Handler) HandleDeviceStatus(device, subject string, data []byte, mtp string, ack func()) { + defer ack() + payload, err := strconv.Atoi(string(data)) + if err != nil { + log.Printf("Status subject payload message error %q", err) + } + + switch payload { + case OFFLINE: + h.deviceOffline(device, mtp) + default: + ignoreMsg(subject, "status", data) + } +} + +func (h *Handler) deviceOffline(device, mtp string) { + log.Printf("Device %s is offline", device) + + mtpLayer := getMtp(mtp) + + err := h.db.UpdateStatus(device, db.Offline, mtpLayer) + if err != nil { + log.Fatal(err) + } +} + +func ignoreMsg(subject, ctx string, data []byte) { + log.Printf("Unknown message of %s received, subject: %s, payload: %s. Ignored...", ctx, subject, string(data)) +} diff --git a/backend/services/mtp/adapter/internal/nats/nats.go b/backend/services/mtp/adapter/internal/nats/nats.go index 7754adf..3a9a56e 100644 --- a/backend/services/mtp/adapter/internal/nats/nats.go +++ b/backend/services/mtp/adapter/internal/nats/nats.go @@ -17,6 +17,7 @@ const ( STOMP_STREAM_NAME = "stomp" LORA_STREAM_NAME = "lora" OPC_STREAM_NAME = "opc" + CWMP_STREAM_NAME = "cwmp" ADAPTER_SUBJECT = "adapter" + USP_SUBJECT USP_SUBJECT = ".usp.v1." BUCKET_NAME = "devices-auth" @@ -118,6 +119,7 @@ func defineStreams() []string { STOMP_STREAM_NAME, LORA_STREAM_NAME, OPC_STREAM_NAME, + CWMP_STREAM_NAME, } } @@ -128,6 +130,7 @@ func defineConsumers() []string { STOMP_STREAM_NAME, LORA_STREAM_NAME, OPC_STREAM_NAME, + CWMP_STREAM_NAME, } } From f596cf9e7b66cf4dfc652e6ebf064092fe7f3e31 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 18 Apr 2024 18:58:02 -0300 Subject: [PATCH 25/30] docs(adapter): readme update --- backend/services/mtp/adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/mtp/adapter/README.md b/backend/services/mtp/adapter/README.md index ceeb185..1592638 100644 --- a/backend/services/mtp/adapter/README.md +++ b/backend/services/mtp/adapter/README.md @@ -1,2 +1,2 @@ -- Abstracts all other mtps existence +- Abstracts all other mtps and cwmp/usp existence - Saves devices info and status \ No newline at end of file From 5487ede44e26538fc6fde039f5a3551870598fd8 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 18 Apr 2024 19:48:49 -0300 Subject: [PATCH 26/30] feat: cwmp device status management --- .../acs/internal/server/handler/handler.go | 9 +--- .../acs/internal/server/handler/status.go | 24 +++++++++ .../mtp/adapter/internal/db/device.go | 53 +++++++++---------- .../mtp/adapter/internal/db/status.go | 6 ++- .../internal/events/cwmp_handler/info.go | 3 +- .../internal/events/cwmp_handler/status.go | 36 +++++++++++++ .../mtp/adapter/internal/events/events.go | 2 +- .../internal/events/usp_handler/info.go | 1 - 8 files changed, 92 insertions(+), 42 deletions(-) create mode 100644 backend/services/acs/internal/server/handler/status.go create mode 100644 backend/services/mtp/adapter/internal/events/cwmp_handler/status.go diff --git a/backend/services/acs/internal/server/handler/handler.go b/backend/services/acs/internal/server/handler/handler.go index 3f040b2..f5e6225 100644 --- a/backend/services/acs/internal/server/handler/handler.go +++ b/backend/services/acs/internal/server/handler/handler.go @@ -29,12 +29,8 @@ type CPE struct { Manufacturer string OUI string ConnectionRequestURL string - XmppId string - XmppUsername string - XmppPassword string SoftwareVersion string ExternalIPAddress string - State string Queue *lane.Queue Waiting *Request HardwareVersion string @@ -86,8 +82,6 @@ func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { body := string(tmp) len := len(body) - //log.Printf("body:\n %v", body) - var envelope cwmp.SoapEnvelope xml.Unmarshal(tmp, &envelope) @@ -136,7 +130,8 @@ func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { DataModel: Inform.GetDataModelType(), KeepConnectionOpen: false, } - h.pub("cwmp.v1."+sn+".info", tmp) //TODO: send right info + go h.handleCpeStatus(sn) + h.pub("cwmp.v1."+sn+".info", tmp) } obj := h.cpes[sn] cpe := &obj diff --git a/backend/services/acs/internal/server/handler/status.go b/backend/services/acs/internal/server/handler/status.go new file mode 100644 index 0000000..1402629 --- /dev/null +++ b/backend/services/acs/internal/server/handler/status.go @@ -0,0 +1,24 @@ +package handler + +import ( + "log" + "time" +) + +// TODO: make these consts dynamic via config +const ( + CHECK_STATUS_INTERVAL = 5 * time.Second + KEEP_ALIVE_INTERVAL = 10 * time.Second +) + +func (h *Handler) handleCpeStatus(cpe string) { + for { + if time.Since(h.cpes[cpe].LastConnection) > KEEP_ALIVE_INTERVAL { + delete(h.cpes, cpe) + break + } + time.Sleep(CHECK_STATUS_INTERVAL) + } + log.Println("CPE", cpe, "is offline") + h.pub("cwmp.v1."+cpe+".status", []byte("0")) +} diff --git a/backend/services/mtp/adapter/internal/db/device.go b/backend/services/mtp/adapter/internal/db/device.go index 0097c63..3fc3cfb 100644 --- a/backend/services/mtp/adapter/internal/db/device.go +++ b/backend/services/mtp/adapter/internal/db/device.go @@ -15,6 +15,7 @@ const ( MQTT STOMP WEBSOCKETS + CWMP ) type Status uint8 @@ -25,15 +26,6 @@ const ( Online ) -type ManagementProtocol uint8 - -const ( - UNKNOWN ManagementProtocol = iota - USP - CWMP - MATTER -) - type Device struct { SN string Model string @@ -45,7 +37,7 @@ type Device struct { Mqtt Status Stomp Status Websockets Status - Protocol ManagementProtocol + Cwmp Status } func (d *Database) CreateDevice(device Device) error { @@ -55,27 +47,28 @@ func (d *Database) CreateDevice(device Device) error { d.m.Lock() defer d.m.Unlock() - if device.Protocol == USP { - /* ------------------ Do not overwrite status of other mtp ------------------ */ - err := d.devices.FindOne(d.ctx, bson.D{{"sn", device.SN}}, nil).Decode(&deviceExistent) - if err == nil { - if deviceExistent.Mqtt == Online { - device.Mqtt = Online - } - if deviceExistent.Stomp == Online { - device.Stomp = Online - } - if deviceExistent.Websockets == Online { - device.Websockets = Online - } - } else { - if err != mongo.ErrNoDocuments { - log.Println(err) - return err - } + /* ------------------ Do not overwrite status of other mtp ------------------ */ + err := d.devices.FindOne(d.ctx, bson.D{{"sn", device.SN}}, nil).Decode(&deviceExistent) + if err == nil { + if deviceExistent.Mqtt == Online { + device.Mqtt = Online + } + if deviceExistent.Stomp == Online { + device.Stomp = Online + } + if deviceExistent.Websockets == Online { + device.Websockets = Online + } + if deviceExistent.Cwmp == Online { + device.Cwmp = Online + } + } else { + if err != mongo.ErrNoDocuments { + log.Println(err) + return err } - /* -------------------------------------------------------------------------- */ } + /* -------------------------------------------------------------------------- */ callback := func(sessCtx mongo.SessionContext) (interface{}, error) { // Important: You must pass sessCtx as the Context parameter to the operations for them to be executed in the @@ -173,6 +166,8 @@ func (m MTP) String() string { return "stomp" case WEBSOCKETS: return "websockets" + case CWMP: + return "cwmp" } return "unknown" } diff --git a/backend/services/mtp/adapter/internal/db/status.go b/backend/services/mtp/adapter/internal/db/status.go index cff6bdf..47ebf29 100644 --- a/backend/services/mtp/adapter/internal/db/status.go +++ b/backend/services/mtp/adapter/internal/db/status.go @@ -35,16 +35,18 @@ func (d *Database) UpdateStatus(sn string, status Status, mtp MTP) error { result.Stomp = status case WEBSOCKETS: result.Websockets = status + case CWMP: + result.Cwmp = status } /* check if the global status needs update */ var globalStatus primitive.E - if result.Mqtt == Offline && result.Stomp == Offline && result.Websockets == Offline { + if result.Mqtt == Offline && result.Stomp == Offline && result.Websockets == Offline && result.Cwmp == Offline { globalStatus = primitive.E{"status", Offline} } - if result.Mqtt == Online || result.Stomp == Online || result.Websockets == Online { + if result.Mqtt == Online || result.Stomp == Online || result.Websockets == Online || result.Cwmp == Online { globalStatus = primitive.E{"status", Online} } diff --git a/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go b/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go index e0f5174..d46a895 100644 --- a/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go +++ b/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go @@ -33,9 +33,8 @@ func parseDeviceInfoMsg(data []byte) db.Device { device.Version = inform.GetSoftwareVersion() device.ProductClass = inform.DeviceId.ProductClass device.SN = inform.DeviceId.SerialNumber - + device.Cwmp = db.Online device.Status = db.Online - device.Protocol = db.CWMP return device } diff --git a/backend/services/mtp/adapter/internal/events/cwmp_handler/status.go b/backend/services/mtp/adapter/internal/events/cwmp_handler/status.go new file mode 100644 index 0000000..4a6502c --- /dev/null +++ b/backend/services/mtp/adapter/internal/events/cwmp_handler/status.go @@ -0,0 +1,36 @@ +package cwmp_handler + +import ( + "log" + "strconv" + + "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" +) + +func (h *Handler) HandleDeviceStatus(device, subject string, data []byte, ack func()) { + defer ack() + payload, err := strconv.Atoi(string(data)) + if err != nil { + log.Printf("Status subject payload message error %q", err) + } + + switch payload { + case OFFLINE: + h.deviceOffline(device) + default: + ignoreMsg(subject, "status", data) + } +} + +func (h *Handler) deviceOffline(device string) { + log.Printf("Device %s is offline", device) + + err := h.db.UpdateStatus(device, db.Offline, db.CWMP) + if err != nil { + log.Fatal(err) + } +} + +func ignoreMsg(subject, ctx string, data []byte) { + log.Printf("Unknown message of %s received, subject: %s, payload: %s. Ignored...", ctx, subject, string(data)) +} diff --git a/backend/services/mtp/adapter/internal/events/events.go b/backend/services/mtp/adapter/internal/events/events.go index 207337a..1148fe8 100644 --- a/backend/services/mtp/adapter/internal/events/events.go +++ b/backend/services/mtp/adapter/internal/events/events.go @@ -94,7 +94,7 @@ func StartEventsListener(ctx context.Context, js jetstream.JetStream, uspHandler switch msgType { case "status": - uspHandler.HandleDeviceStatus(device, msg.Subject(), data, event, func() { msg.Ack() }) + cwmpHandler.HandleDeviceStatus(device, msg.Subject(), data, func() { msg.Ack() }) case "info": cwmpHandler.HandleDeviceInfo(device, data, func() { msg.Ack() }) default: diff --git a/backend/services/mtp/adapter/internal/events/usp_handler/info.go b/backend/services/mtp/adapter/internal/events/usp_handler/info.go index 6024f49..07acca7 100644 --- a/backend/services/mtp/adapter/internal/events/usp_handler/info.go +++ b/backend/services/mtp/adapter/internal/events/usp_handler/info.go @@ -64,7 +64,6 @@ func parseDeviceInfoMsg(sn, subject string, data []byte, mtp db.MTP) db.Device { } device.Status = db.Online - device.Protocol = db.USP return device } From 406d62550f119b1af4321f4eb6b1650c551cfc27 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Sat, 20 Apr 2024 11:20:38 -0300 Subject: [PATCH 27/30] feat: initial api for cwmp --- backend/services/acs/cmd/main.go | 8 + backend/services/acs/go.mod | 1 + backend/services/acs/go.sum | 2 + backend/services/acs/internal/auth/auth.go | 57 +- .../services/acs/internal/bridge/bridge.go | 112 ++++ .../acs/internal/server/handler/cwmp.go | 164 ++++++ .../acs/internal/server/handler/handler.go | 171 +----- .../acs/internal/server/handler/status.go | 8 +- .../services/acs/internal/server/server.go | 1 - .../services/controller/internal/api/api.go | 1 + .../services/controller/internal/api/cwmp.go | 48 ++ .../controller/internal/bridge/bridge.go | 42 ++ .../services/controller/internal/cwmp/cwmp.go | 542 ++++++++++++++++++ .../controller/internal/entity/msg.go | 2 +- .../services/controller/internal/nats/nats.go | 2 + 15 files changed, 971 insertions(+), 190 deletions(-) create mode 100644 backend/services/acs/internal/bridge/bridge.go create mode 100644 backend/services/acs/internal/server/handler/cwmp.go create mode 100644 backend/services/controller/internal/api/cwmp.go create mode 100644 backend/services/controller/internal/cwmp/cwmp.go diff --git a/backend/services/acs/cmd/main.go b/backend/services/acs/cmd/main.go index 3ab03b9..53d9379 100644 --- a/backend/services/acs/cmd/main.go +++ b/backend/services/acs/cmd/main.go @@ -1,6 +1,7 @@ package main import ( + "oktopUSP/backend/services/acs/internal/bridge" "oktopUSP/backend/services/acs/internal/config" "oktopUSP/backend/services/acs/internal/nats" "oktopUSP/backend/services/acs/internal/server" @@ -15,5 +16,12 @@ func main() { h := handler.NewHandler(natsActions.Publish, natsActions.Subscribe) + b := bridge.NewBridge( + natsActions.Publish, + natsActions.Subscribe, + h, + ) + b.StartBridge() + server.Run(c.Acs, natsActions, h) } diff --git a/backend/services/acs/go.mod b/backend/services/acs/go.mod index 5ba5c09..f57be7f 100644 --- a/backend/services/acs/go.mod +++ b/backend/services/acs/go.mod @@ -3,6 +3,7 @@ module oktopUSP/backend/services/acs go 1.22.2 require ( + github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/nats-io/nats.go v1.34.1 github.com/oleiade/lane v1.0.1 diff --git a/backend/services/acs/go.sum b/backend/services/acs/go.sum index f09390a..c223b41 100644 --- a/backend/services/acs/go.sum +++ b/backend/services/acs/go.sum @@ -1,3 +1,5 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= diff --git a/backend/services/acs/internal/auth/auth.go b/backend/services/acs/internal/auth/auth.go index 476936f..3b20e03 100644 --- a/backend/services/acs/internal/auth/auth.go +++ b/backend/services/acs/internal/auth/auth.go @@ -5,7 +5,6 @@ import ( "crypto/rand" "encoding/base64" "fmt" - "io" "net/http" "net/url" "strings" @@ -36,37 +35,37 @@ func Auth(username string, password string, uri string) (bool, error) { if err != nil { return false, err } - if resp.StatusCode == 401 { - var authorization map[string]string = DigestAuthParams(resp) - realmHeader := authorization["realm"] - qopHeader := authorization["qop"] - nonceHeader := authorization["nonce"] - opaqueHeader := authorization["opaque"] - realm := realmHeader - // A1 - h := md5.New() - A1 := fmt.Sprintf("%s:%s:%s", username, realm, password) - io.WriteString(h, A1) - HA1 := fmt.Sprintf("%x", h.Sum(nil)) + // if resp.StatusCode == 401 { + // var authorization map[string]string = DigestAuthParams(resp) + // realmHeader := authorization["realm"] + // qopHeader := authorization["qop"] + // nonceHeader := authorization["nonce"] + // opaqueHeader := authorization["opaque"] + // realm := realmHeader + // // A1 + // h := md5.New() + // A1 := fmt.Sprintf("%s:%s:%s", username, realm, password) + // io.WriteString(h, A1) + // HA1 := fmt.Sprintf("%x", h.Sum(nil)) - // A2 - h = md5.New() - A2 := fmt.Sprintf("GET:%s", "/auth") - io.WriteString(h, A2) - HA2 := fmt.Sprintf("%x", h.Sum(nil)) + // // A2 + // h = md5.New() + // A2 := fmt.Sprintf("GET:%s", "/auth") + // io.WriteString(h, A2) + // HA2 := fmt.Sprintf("%x", h.Sum(nil)) - // response - cnonce := RandomKey() - response := H(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")) + // // response + // cnonce := RandomKey() + // response := H(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")) - // now make header - AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s", opaque="%s", algorithm=MD5`, - username, realmHeader, nonceHeader, "/auth", cnonce, qopHeader, response, opaqueHeader) - req.Header.Set("Authorization", AuthHeader) - resp, err = client.Do(req) - } else { - return false, fmt.Errorf("response status code should have been 401, it was %v", resp.StatusCode) - } + // // now make header + // AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s", opaque="%s", algorithm=MD5`, + // username, realmHeader, nonceHeader, "/auth", cnonce, qopHeader, response, opaqueHeader) + // req.Header.Set("Authorization", AuthHeader) + // resp, err = client.Do(req) + // } else { + // return false, fmt.Errorf("response status code should have been 401, it was %v", resp.StatusCode) + // } return resp.StatusCode == 200, err } diff --git a/backend/services/acs/internal/bridge/bridge.go b/backend/services/acs/internal/bridge/bridge.go new file mode 100644 index 0000000..9eda542 --- /dev/null +++ b/backend/services/acs/internal/bridge/bridge.go @@ -0,0 +1,112 @@ +package bridge + +import ( + "encoding/json" + "log" + "net/http" + "oktopUSP/backend/services/acs/internal/server/handler" + "strings" + "time" + + "github.com/google/uuid" + "github.com/nats-io/nats.go" +) + +type Bridge struct { + pub func(string, []byte) error + sub func(string, func(*nats.Msg)) error + cpes map[string]handler.CPE + h *handler.Handler +} + +type msgAnswer struct { + Code int + Msg any +} + +const DEVICE_ANSWER_TIMEOUT = 5 * time.Second + +func NewBridge( + pub func(string, []byte) error, + sub func(string, func(*nats.Msg)) error, + h *handler.Handler, +) *Bridge { + return &Bridge{ + pub: pub, + sub: sub, + cpes: h.Cpes, + h: h, + } +} + +func (b *Bridge) StartBridge() { + b.sub(handler.NATS_CWMP_ADAPTER_SUBJECT_PREFIX+"*.api", func(msg *nats.Msg) { + log.Printf("Received message: %s", string(msg.Data)) + log.Printf("Subject: %s", msg.Subject) + log.Printf("Reply: %s", msg.Reply) + + device := getDeviceFromSubject(msg.Subject) + cpe, ok := b.cpes[device] + if !ok { + log.Printf("Device %s not found", device) + respondMsg(msg.Respond, http.StatusNotFound, "Device not found") + return + } + if cpe.Queue.Size() > 0 { + log.Printf("Device %s is busy", device) + respondMsg(msg.Respond, http.StatusConflict, "Device is busy") + return + } + + deviceAnswer := make(chan []byte) + defer close(deviceAnswer) + + cpe.Queue.Enqueue(handler.Request{ //TODO: pass user and password too + Id: uuid.NewString(), + CwmpMsg: msg.Data, + Callback: deviceAnswer, + }) + + err := b.h.ConnectionRequest(cpe) + if err != nil { + log.Println("Failed to do connection request", err) + cpe.Queue.Dequeue() + respondMsg(msg.Respond, http.StatusBadRequest, err.Error()) + return + } + + //req := cpe.Queue.Dequeue().(handler.Request) + //cpe.Waiting = &req + + select { + case response := <-deviceAnswer: + log.Println("Received response from device: ", string(response)) + respondMsg(msg.Respond, http.StatusOK, response) + case <-time.After(DEVICE_ANSWER_TIMEOUT): + log.Println("Device response timed out") + respondMsg(msg.Respond, http.StatusRequestTimeout, "Request timeout") + } + }) +} + +func respondMsg(respond func(data []byte) error, code int, msgData any) { + + msg, err := json.Marshal(msgAnswer{ + Code: code, + Msg: msgData, + }) + if err != nil { + log.Printf("Failed to marshal message: %q", err) + respond([]byte(err.Error())) + return + } + + respond(msg) + //log.Println("Responded with message: ", string(msg)) +} + +func getDeviceFromSubject(subject string) string { + paths := strings.Split(subject, ".") + device := paths[len(paths)-2] + return device +} diff --git a/backend/services/acs/internal/server/handler/cwmp.go b/backend/services/acs/internal/server/handler/cwmp.go new file mode 100644 index 0000000..c682353 --- /dev/null +++ b/backend/services/acs/internal/server/handler/cwmp.go @@ -0,0 +1,164 @@ +package handler + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "log" + "net/http" + "oktopUSP/backend/services/acs/internal/auth" + "oktopUSP/backend/services/acs/internal/cwmp" + "time" + + "github.com/oleiade/lane" +) + +func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { + + log.Printf("--> Connection from %s", r.RemoteAddr) + + defer r.Body.Close() + defer log.Printf("<-- Connection from %s closed", r.RemoteAddr) + + tmp, _ := ioutil.ReadAll(r.Body) + body := string(tmp) + + var envelope cwmp.SoapEnvelope + xml.Unmarshal(tmp, &envelope) + + messageType := envelope.Body.CWMPMessage.XMLName.Local + log.Println("messageType: ", messageType) + + var cpe CPE + var exists bool + + w.Header().Set("Server", "Oktopus "+Version) + + if messageType != "Inform" { + if cookie, err := r.Cookie("oktopus"); err == nil { + if cpe, exists = h.Cpes[cookie.Value]; !exists { + log.Printf("CPE with serial number %s not found", cookie.Value) + } + log.Printf("CPE with serial number %s found", cookie.Value) + } else { + fmt.Println("cookie 'oktopus' missing") + w.WriteHeader(401) + return + } + } + + if messageType == "Inform" { + var Inform cwmp.CWMPInform + xml.Unmarshal(tmp, &Inform) + + var addr string + if r.Header.Get("X-Real-Ip") != "" { + addr = r.Header.Get("X-Real-Ip") + } else { + addr = r.RemoteAddr + } + + sn := Inform.DeviceId.SerialNumber + + if _, exists := h.Cpes[sn]; !exists { + log.Println("New device: " + sn) + h.Cpes[sn] = CPE{ + SerialNumber: sn, + LastConnection: time.Now().UTC(), + SoftwareVersion: Inform.GetSoftwareVersion(), + HardwareVersion: Inform.GetHardwareVersion(), + ExternalIPAddress: addr, + ConnectionRequestURL: Inform.GetConnectionRequest(), + OUI: Inform.DeviceId.OUI, + Queue: lane.NewQueue(), + DataModel: Inform.GetDataModelType(), + } + go h.handleCpeStatus(sn) + h.pub(NATS_CWMP_SUBJECT_PREFIX+sn+".info", tmp) + } + obj := h.Cpes[sn] + cpe := &obj + cpe.LastConnection = time.Now().UTC() + + log.Printf("Received an Inform from device %s withEventCodes %s", addr, Inform.GetEvents()) + + expiration := time.Now().AddDate(0, 0, 1) + + cookie := http.Cookie{Name: "oktopus", Value: sn, Expires: expiration} + http.SetCookie(w, &cookie) + data, _ := xml.Marshal(cwmp.InformResponse(envelope.Header.Id)) + w.Write(data) + } else if messageType == "TransferComplete" { + + } else if messageType == "GetRPC" { + + } else { + + if len(body) == 0 { + log.Println("Got Empty Post") + } + + if cpe.Waiting != nil { + log.Println("CPE was waiting for a response, now received something") + var e cwmp.SoapEnvelope + xml.Unmarshal([]byte(body), &e) + log.Println("Kind of envelope: ", e.KindOf()) + + if e.KindOf() == "GetParameterNamesResponse" { + // var envelope cwmp.GetParameterNamesResponse + // xml.Unmarshal([]byte(body), &envelope) + + // msg := new(NatsSendMessage) + // msg.MsgType = "GetParameterNamesResponse" + // msg.Data, _ = json.Marshal(envelope) + log.Println("Receive GetParameterNamesResponse from CPE:", cpe.SerialNumber) + cpe.Waiting.Callback <- tmp + + } else if e.KindOf() == "GetParameterValuesResponse" { + var envelope cwmp.GetParameterValuesResponse + xml.Unmarshal([]byte(body), &envelope) + + msg := new(NatsSendMessage) + msg.MsgType = "GetParameterValuesResponse" + msg.Data, _ = json.Marshal(envelope) + + cpe.Waiting.Callback <- tmp + + } else { + log.Println("Unknown message type") + cpe.Waiting.Callback <- tmp + } + cpe.Waiting = nil + } else { + log.Println("CPE was not waiting for a response") + } + + log.Printf("CPE %s Queue size: %d", cpe.SerialNumber, cpe.Queue.Size()) + + if cpe.Queue.Size() > 0 { + req := cpe.Queue.Dequeue().(Request) + cpe.Waiting = &req + log.Println("Sending request to CPE:", req.Id) + w.Header().Set("Connection", "keep-alive") + w.Write(req.CwmpMsg) + } else { + w.Header().Set("Connection", "close") + w.WriteHeader(204) + } + } + h.Cpes[cpe.SerialNumber] = cpe +} + +func (h *Handler) ConnectionRequest(cpe CPE) error { + log.Println("--> ConnectionRequest, CPE: ", cpe.SerialNumber) + // log.Println("ConnectionRequestURL: ", cpe.ConnectionRequestURL) + // log.Println("ConnectionRequestUsername: ", cpe.Username) + // log.Println("ConnectionRequestPassword: ", cpe.Password) + + ok, err := auth.Auth("", "", cpe.ConnectionRequestURL) + if !ok { + log.Println("Error while authenticating to CPE: ", err) + } + return err +} diff --git a/backend/services/acs/internal/server/handler/handler.go b/backend/services/acs/internal/server/handler/handler.go index f5e6225..5d1fd00 100644 --- a/backend/services/acs/internal/server/handler/handler.go +++ b/backend/services/acs/internal/server/handler/handler.go @@ -2,26 +2,20 @@ package handler import ( "encoding/json" - "encoding/xml" - "fmt" - "io/ioutil" - "log" - "net/http" - "oktopUSP/backend/services/acs/internal/cwmp" "time" "github.com/nats-io/nats.go" "github.com/oleiade/lane" - "golang.org/x/net/websocket" ) const Version = "1.0.0" type Request struct { - Id string - Websocket *websocket.Conn - CwmpMessage string - Callback func(msg *WsSendMessage) error + Id string + User string + Password string + CwmpMsg []byte + Callback chan []byte } type CPE struct { @@ -36,7 +30,8 @@ type CPE struct { HardwareVersion string LastConnection time.Time DataModel string - KeepConnectionOpen bool + Username string + Password string } type Message struct { @@ -48,7 +43,7 @@ type WsMessage struct { Cmd string } -type WsSendMessage struct { +type NatsSendMessage struct { MsgType string Data json.RawMessage } @@ -60,153 +55,19 @@ type MsgCPEs struct { type Handler struct { pub func(string, []byte) error sub func(string, func(*nats.Msg)) error - cpes map[string]CPE + Cpes map[string]CPE } +const ( + NATS_CWMP_SUBJECT_PREFIX = "cwmp.v1." + NATS_CWMP_ADAPTER_SUBJECT_PREFIX = "cwmp-adapter.v1." + NATS_ADAPTER_SUBJECT_PREFIX = "adapter.v1." +) + func NewHandler(pub func(string, []byte) error, sub func(string, func(*nats.Msg)) error) *Handler { return &Handler{ pub: pub, sub: sub, - cpes: make(map[string]CPE), - } -} - -func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { - - log.Printf("--> Connection from %s", r.RemoteAddr) - - defer r.Body.Close() - defer log.Printf("<-- Connection from %s closed", r.RemoteAddr) - - tmp, _ := ioutil.ReadAll(r.Body) - body := string(tmp) - len := len(body) - - var envelope cwmp.SoapEnvelope - xml.Unmarshal(tmp, &envelope) - - messageType := envelope.Body.CWMPMessage.XMLName.Local - - var cpe *CPE - - w.Header().Set("Server", "Oktopus "+Version) - - if messageType != "Inform" { - if cookie, err := r.Cookie("oktopus"); err == nil { - if _, exists := h.cpes[cookie.Value]; !exists { - log.Printf("CPE with serial number %s not found", cookie.Value) - } - } else { - fmt.Println("cookie 'oktopus' missing") - w.WriteHeader(401) - return - } - } - - if messageType == "Inform" { - var Inform cwmp.CWMPInform - xml.Unmarshal(tmp, &Inform) - - var addr string - if r.Header.Get("X-Real-Ip") != "" { - addr = r.Header.Get("X-Real-Ip") - } else { - addr = r.RemoteAddr - } - - sn := Inform.DeviceId.SerialNumber - - if _, exists := h.cpes[sn]; !exists { - fmt.Println("New device: " + sn) - h.cpes[sn] = CPE{ - SerialNumber: sn, - LastConnection: time.Now().UTC(), - SoftwareVersion: Inform.GetSoftwareVersion(), - HardwareVersion: Inform.GetHardwareVersion(), - ExternalIPAddress: addr, - ConnectionRequestURL: Inform.GetConnectionRequest(), - OUI: Inform.DeviceId.OUI, - Queue: lane.NewQueue(), - DataModel: Inform.GetDataModelType(), - KeepConnectionOpen: false, - } - go h.handleCpeStatus(sn) - h.pub("cwmp.v1."+sn+".info", tmp) - } - obj := h.cpes[sn] - cpe := &obj - cpe.LastConnection = time.Now().UTC() - - log.Printf("Received an Inform from %s (%d bytes) with SerialNumber %s and EventCodes %s", addr, len, sn, Inform.GetEvents()) - - expiration := time.Now().AddDate(0, 0, 1) - - cookie := http.Cookie{Name: "oktopus", Value: sn, Expires: expiration} - http.SetCookie(w, &cookie) - } else if messageType == "TransferComplete" { - - } else if messageType == "GetRPC" { - - } else { - if len == 0 { - log.Printf("Got Empty Post") - } - - if cpe.Waiting != nil { - var e cwmp.SoapEnvelope - xml.Unmarshal([]byte(body), &e) - - if e.KindOf() == "GetParameterNamesResponse" { - var envelope cwmp.GetParameterNamesResponse - xml.Unmarshal([]byte(body), &envelope) - - msg := new(WsSendMessage) - msg.MsgType = "GetParameterNamesResponse" - msg.Data, _ = json.Marshal(envelope) - - cpe.Waiting.Callback(msg) - // if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { - // fmt.Println("error while sending back answer:", err) - // } - - } else if e.KindOf() == "GetParameterValuesResponse" { - var envelope cwmp.GetParameterValuesResponse - xml.Unmarshal([]byte(body), &envelope) - - msg := new(WsSendMessage) - msg.MsgType = "GetParameterValuesResponse" - msg.Data, _ = json.Marshal(envelope) - - cpe.Waiting.Callback(msg) - // if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { - // fmt.Println("error while sending back answer:", err) - // } - - } else { - msg := new(WsMessage) - msg.Cmd = body - - if err := websocket.JSON.Send(cpe.Waiting.Websocket, msg); err != nil { - fmt.Println("error while sending back answer:", err) - } - - } - - cpe.Waiting = nil - } - - // Got Empty Post or a Response. Now check for any event to send, otherwise 204 - if cpe.Queue.Size() > 0 { - req := cpe.Queue.Dequeue().(Request) - // fmt.Println("sending "+req.CwmpMessage) - fmt.Fprintf(w, req.CwmpMessage) - cpe.Waiting = &req - } else { - if cpe.KeepConnectionOpen { - fmt.Println("I'm keeping connection open") - } else { - w.WriteHeader(204) - } - } + Cpes: make(map[string]CPE), } } diff --git a/backend/services/acs/internal/server/handler/status.go b/backend/services/acs/internal/server/handler/status.go index 1402629..ec4c7a1 100644 --- a/backend/services/acs/internal/server/handler/status.go +++ b/backend/services/acs/internal/server/handler/status.go @@ -7,14 +7,14 @@ import ( // TODO: make these consts dynamic via config const ( - CHECK_STATUS_INTERVAL = 5 * time.Second - KEEP_ALIVE_INTERVAL = 10 * time.Second + CHECK_STATUS_INTERVAL = 10 * time.Second + KEEP_ALIVE_INTERVAL = 600 * time.Second ) func (h *Handler) handleCpeStatus(cpe string) { for { - if time.Since(h.cpes[cpe].LastConnection) > KEEP_ALIVE_INTERVAL { - delete(h.cpes, cpe) + if time.Since(h.Cpes[cpe].LastConnection) > KEEP_ALIVE_INTERVAL { + delete(h.Cpes, cpe) break } time.Sleep(CHECK_STATUS_INTERVAL) diff --git a/backend/services/acs/internal/server/server.go b/backend/services/acs/internal/server/server.go index 4a2bc6b..8022e2a 100644 --- a/backend/services/acs/internal/server/server.go +++ b/backend/services/acs/internal/server/server.go @@ -12,7 +12,6 @@ import ( func Run(c config.Acs, natsActions nats.NatsActions, h *handler.Handler) { http.HandleFunc(c.Route, h.CwmpHandler) - log.Printf("ACS running at %s%s", c.Port, c.Route) err := http.ListenAndServe(c.Port, nil) diff --git a/backend/services/controller/internal/api/api.go b/backend/services/controller/internal/api/api.go index 33c25fe..bd20320 100644 --- a/backend/services/controller/internal/api/api.go +++ b/backend/services/controller/internal/api/api.go @@ -57,6 +57,7 @@ func (a *Api) StartApi() { authentication.HandleFunc("/admin/exists", a.adminUserExists).Methods("GET") iot := r.PathPrefix("/api/device").Subrouter() iot.HandleFunc("/auth", a.deviceAuth).Methods("GET", "PUT", "DELETE") + iot.HandleFunc("/cwmp/{sn}/getParameterNames", a.cwmpGetParameterNamesMsg).Methods("GET", "PUT", "DELETE") iot.HandleFunc("", a.retrieveDevices).Methods("GET") iot.HandleFunc("/{id}", a.retrieveDevices).Methods("GET") iot.HandleFunc("/{sn}/{mtp}/get", a.deviceGetMsg).Methods("PUT") diff --git a/backend/services/controller/internal/api/cwmp.go b/backend/services/controller/internal/api/cwmp.go new file mode 100644 index 0000000..6b1ba31 --- /dev/null +++ b/backend/services/controller/internal/api/cwmp.go @@ -0,0 +1,48 @@ +package api + +import ( + "encoding/json" + "encoding/xml" + "io" + "net/http" + + "github.com/leandrofars/oktopus/internal/bridge" + "github.com/leandrofars/oktopus/internal/cwmp" + "github.com/leandrofars/oktopus/internal/nats" + "github.com/leandrofars/oktopus/internal/utils" +) + +func (a *Api) cwmpGetParameterNamesMsg(w http.ResponseWriter, r *http.Request) { + sn := getSerialNumberFromRequest(r) + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + data, err := bridge.NatsCwmpInteraction( + nats.NATS_CWMP_ADAPTER_SUBJECT_PREFIX+sn+".api", + payload, + w, + a.nc, + ) + if err != nil { + return + } + + var response cwmp.GetParameterNamesResponse + err = xml.Unmarshal(data, &response) + if err != nil { + err = json.Unmarshal(data, &response) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err)) + return + } + return + } + + w.Write(data) +} diff --git a/backend/services/controller/internal/bridge/bridge.go b/backend/services/controller/internal/bridge/bridge.go index af3bc1a..319ef13 100644 --- a/backend/services/controller/internal/bridge/bridge.go +++ b/backend/services/controller/internal/bridge/bridge.go @@ -204,3 +204,45 @@ func NatsReqWithoutHttpSet[T entity.DataType]( return answer, nil } + +func NatsCwmpInteraction( + subj string, + body []byte, + w http.ResponseWriter, + nc *nats.Conn, +) ([]byte, error) { + + log.Println("Sending cwmp message") + log.Println("Subject: ", subj) + + var answer entity.MsgAnswer[[]byte] + + msg, err := nc.Request(subj, body, local.NATS_REQUEST_TIMEOUT) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(utils.Marshall("Error to communicate with nats: " + err.Error())) + return nil, err + } + + err = json.Unmarshal(msg.Data, &answer) + if err != nil { + + var errMsg *entity.MsgAnswer[*string] + err = json.Unmarshal(msg.Data, &errMsg) + + if err != nil { + log.Println("Bad answer message formatting: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write(msg.Data) + return nil, err + } + + log.Printf("Error message received, msg: %s, code: %d", *errMsg.Msg, errMsg.Code) + w.WriteHeader(errMsg.Code) + w.Write(utils.Marshall(*errMsg.Msg)) + return nil, errNatsMsgReceivedWithErrorData + } + + return answer.Msg, nil +} diff --git a/backend/services/controller/internal/cwmp/cwmp.go b/backend/services/controller/internal/cwmp/cwmp.go new file mode 100644 index 0000000..a0aaa06 --- /dev/null +++ b/backend/services/controller/internal/cwmp/cwmp.go @@ -0,0 +1,542 @@ +package cwmp + +import ( + "crypto/rand" + "encoding/xml" + "fmt" + "strconv" + "strings" + "time" +) + +type SoapEnvelope struct { + XMLName xml.Name + Header SoapHeader + Body SoapBody +} + +type SoapHeader struct { + Id string `xml:"ID"` +} +type SoapBody struct { + CWMPMessage CWMPMessage `xml:",any"` +} + +type CWMPMessage struct { + XMLName xml.Name +} + +type EventStruct struct { + EventCode string + CommandKey string +} + +type ParameterValueStruct struct { + Name string + Value string +} + +type ParameterInfoStruct struct { + Name string + Writable string +} + +type SetParameterValues_ struct { + ParameterList []ParameterValueStruct `xml:"Body>SetParameterValues>ParameterList>ParameterValueStruct"` + ParameterKey string `xml:"Body>SetParameterValues>ParameterKey>string"` +} + +type GetParameterValues_ struct { + ParameterNames []string `xml:"Body>GetParameterValues>ParameterNames>string"` +} + +type GetParameterNames_ struct { + ParameterPath []string `xml:"Body>GetParameterNames>ParameterPath"` + NextLevel string `xml:"Body>GetParameterNames>NextLevel"` +} + +type GetParameterValuesResponse struct { + ParameterList []ParameterValueStruct `xml:"Body>GetParameterValuesResponse>ParameterList>ParameterValueStruct"` +} + +type GetParameterNamesResponse struct { + ParameterList []ParameterInfoStruct `xml:"Body>GetParameterNamesResponse>ParameterList>ParameterInfoStruct"` +} + +type CWMPInform struct { + DeviceId DeviceID `xml:"Body>Inform>DeviceId"` + Events []EventStruct `xml:"Body>Inform>Event>EventStruct"` + ParameterList []ParameterValueStruct `xml:"Body>Inform>ParameterList>ParameterValueStruct"` +} + +func (s *SoapEnvelope) KindOf() string { + return s.Body.CWMPMessage.XMLName.Local +} + +func (i *CWMPInform) GetEvents() string { + res := "" + for idx := range i.Events { + res += i.Events[idx].EventCode + } + + return res +} + +func (i *CWMPInform) GetConnectionRequest() string { + for idx := range i.ParameterList { + // valid condition for both tr98 and tr181 + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.ManagementServer.ConnectionRequestURL") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetSoftwareVersion() string { + for idx := range i.ParameterList { + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.SoftwareVersion") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetHardwareVersion() string { + for idx := range i.ParameterList { + if strings.HasSuffix(i.ParameterList[idx].Name, "Device.DeviceInfo.HardwareVersion") { + return i.ParameterList[idx].Value + } + } + + return "" +} + +func (i *CWMPInform) GetDataModelType() string { + if strings.HasPrefix(i.ParameterList[0].Name, "InternetGatewayDevice") { + return "TR098" + } else if strings.HasPrefix(i.ParameterList[0].Name, "Device") { + return "TR181" + } + + return "" +} + +type DeviceID struct { + Manufacturer string + OUI string + SerialNumber string + ProductClass string +} + +func InformResponse(mustUnderstand string) string { + mustUnderstandHeader := "" + if mustUnderstand != "" { + mustUnderstandHeader = `` + mustUnderstand + `` + } + + return ` + + ` + mustUnderstandHeader + ` + + + 1 + + +` +} + +func GetParameterValues(leaf string) string { + return ` + + + + + + ` + leaf + ` + + + +` +} + +func GetParameterMultiValues(leaves []string) string { + msg := ` + + + + + ` + + for idx := range leaves { + msg += `` + leaves[idx] + `` + + } + msg += ` + + +` + return msg +} + +func SetParameterValues(leaf string, value string) string { + return ` + + + + + + + ` + leaf + ` + ` + value + ` + + + LC1309` + randToken() + ` + + +` +} + +func randToken() string { + b := make([]byte, 8) + rand.Read(b) + return fmt.Sprintf("%x", b) +} + +func SetParameterMultiValues(data map[string]string) string { + msg := ` + + + + + ` + + for key, value := range data { + msg += ` + ` + key + ` + ` + value + ` + ` + } + + msg += ` + LC1309` + randToken() + ` + + +` + + return msg +} + +func GetParameterNames(leaf string, nextlevel int) string { + return ` + + + + + ` + leaf + ` + ` + strconv.Itoa(nextlevel) + ` + + +` +} + +func FactoryReset() string { + return ` + + + + + +` +} + +func Download(filetype, url, username, password, filesize string) string { + // 3 Vendor Configuration File + // 1 Firmware Upgrade Image + + return ` + + + + + MSDWK + ` + filetype + ` + ` + url + ` + ` + username + ` + ` + password + ` + ` + filesize + ` + + 0 + + + + +` +} + +func CancelTransfer() string { + return ` + + + + + + + +` +} + +type TimeWindowStruct struct { + WindowStart string + WindowEnd string + WindowMode string + UserMessage string + MaxRetries string +} + +func (window *TimeWindowStruct) String() string { + return ` +` + window.WindowStart + ` +` + window.WindowEnd + ` +` + window.WindowMode + ` +` + window.UserMessage + ` +` + window.MaxRetries + ` +` +} + +func ScheduleDownload(filetype, url, username, password, filesize string, windowslist []fmt.Stringer) string { + ret := ` + + + + + MSDWK + ` + filetype + ` + ` + url + ` + ` + username + ` + ` + password + ` + ` + filesize + ` + + ` + + for _, op := range windowslist { + ret += op.String() + } + + ret += ` + + +` + + return ret +} + +type InstallOpStruct struct { + Url string + Uuid string + Username string + Password string + ExecutionEnvironment string +} + +func (op *InstallOpStruct) String() string { + return ` + ` + op.Url + ` + ` + op.Uuid + ` + ` + op.Username + ` + ` + op.Password + ` + ` + op.ExecutionEnvironment + ` +` +} + +type UpdateOpStruct struct { + Uuid string + Version string + Url string + Username string + Password string +} + +func (op *UpdateOpStruct) String() string { + return ` +` + op.Uuid + ` +` + op.Version + ` +` + op.Url + ` +` + op.Username + ` +` + op.Password + ` +` +} + +type UninstallOpStruct struct { + Uuid string + Version string + ExecutionEnvironment string +} + +func (op *UninstallOpStruct) String() string { + return ` +` + op.Uuid + ` +` + op.Version + ` +` + op.ExecutionEnvironment + ` +` +} + +func ChangeDuState(ops []fmt.Stringer) string { + ret := ` + + + + +` + + for _, op := range ops { + ret += op.String() + } + + ret += ` + + + +` + + return ret +} + +// CPE side + +func Inform(serial string) string { + return `5058 + ADB Broadband +0013C8 +VV5522 +` + serial + ` + + +6 CONNECTION REQUEST + + + +1 +` + time.Now().Format(time.RFC3339) + ` +0 + +InternetGatewayDevice.ManagementServer.ConnectionRequestURL +http://104.199.175.27:7547/ConnectionRequest-` + serial + ` + +InternetGatewayDevice.ManagementServer.ParameterKey + + +InternetGatewayDevice.DeviceSummary +InternetGatewayDevice:1.2[](Baseline:1,EthernetLAN:1,WiFiLAN:1,ADSLWAN:1,EthernetWAN:1,QoS:1,QoSDynamicFlow:1,Bridging:1,Time:1,IPPing:1,TraceRoute:1,DeviceAssociation:1,UDPConnReq:1),VoiceService:1.0[1](TAEndpoint:1,SIPEndpoint:1) + +InternetGatewayDevice.DeviceInfo.HardwareVersion +` + serial + ` + +InternetGatewayDevice.DeviceInfo.ProvisioningCode +ABCD + +InternetGatewayDevice.DeviceInfo.SoftwareVersion +4.0.8.17785 + +InternetGatewayDevice.DeviceInfo.SpecVersion +1.0 + +InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress +12.0.0.10 + + + +` +} + +/* +func BuildGetParameterValuesResponse(serial string, leaves GetParameterValues_) string { + ret := ` + 3 + ` + + db, _ := sqlite3.Open("/tmp/cpe.db") + + n_leaves := 0 + var temp string + for _, leaf := range leaves.ParameterNames { + sql := "select key, value, tipo from params where key like '" + leaf + "%'" + for s, err := db.Query(sql); err == nil; err = s.Next() { + n_leaves++ + var key string + var value string + var tipo string + s.Scan(&key, &value, &tipo) + temp += ` + ` + key + ` + ` + value + ` + ` + } + } + + ret += `` + ret += temp + ret += `` + + return ret +} + +func BuildGetParameterNamesResponse(serial string, leaves GetParameterNames_) string { + ret := ` + 69 + ` + db, _ := sqlite3.Open("/tmp/cpe.db") + + obj := make(map[string]bool) + var temp string + for _, leaf := range leaves.ParameterPath { + fmt.Println(leaf) + sql := "select key, value, tipo from params where key like '" + leaf + "%'" + for s, err := db.Query(sql); err == nil; err = s.Next() { + var key string + var value string + var tipo string + s.Scan(&key, &value, &tipo) + var sp = strings.Split(strings.Split(key, leaf)[1], ".") + nextlevel, _ := strconv.Atoi(leaves.NextLevel) + if nextlevel == 0 { + root := leaf + obj[root] = true + for idx := range sp { + if idx == len(sp)-1 { + root = root + sp[idx] + } else { + root = root + sp[idx] + "." + } + obj[root] = true + } + } else { + if !obj[sp[0]] { + if len(sp) > 1 { + obj[leaf+sp[0]+"."] = true + } else { + obj[leaf+sp[0]] = true + } + + } + } + + } + } + + for o := range obj { + temp += ` + ` + o + ` + true + ` + } + + fmt.Println(len(obj)) + ret += `` + ret += temp + ret += `` + + return ret +} +*/ diff --git a/backend/services/controller/internal/entity/msg.go b/backend/services/controller/internal/entity/msg.go index a3e7eda..768e4bb 100644 --- a/backend/services/controller/internal/entity/msg.go +++ b/backend/services/controller/internal/entity/msg.go @@ -3,7 +3,7 @@ package entity import "time" type DataType interface { - []map[string]interface{} | *string | Device | int64 | []Device | []VendorsCount | []ProductClassCount | []StatusCount | time.Duration + []map[string]interface{} | *string | Device | int64 | []Device | []VendorsCount | []ProductClassCount | []StatusCount | time.Duration | []byte } type MsgAnswer[T DataType] struct { diff --git a/backend/services/controller/internal/nats/nats.go b/backend/services/controller/internal/nats/nats.go index f2b4998..3786604 100644 --- a/backend/services/controller/internal/nats/nats.go +++ b/backend/services/controller/internal/nats/nats.go @@ -19,8 +19,10 @@ const ( NATS_WS_ADAPTER_SUBJECT_PREFIX = "ws-adapter.usp.v1." NATS_STOMP_ADAPTER_SUBJECT_PREFIX = "stomp-adapter.usp.v1." DEVICE_SUBJECT_PREFIX = "device.usp.v1." + DEVICE_CWMP_SUBJECT_PREFIX = "device.cwmp.v1." BUCKET_NAME = "devices-auth" BUCKET_DESCRIPTION = "Devices authentication" + NATS_CWMP_ADAPTER_SUBJECT_PREFIX = "cwmp-adapter.v1." ) func StartNatsClient(c config.Nats) (jetstream.JetStream, *nats.Conn, jetstream.KeyValue) { From 02b66385bcf630d3b678175e10c230f0956b39fd Mon Sep 17 00:00:00 2001 From: leandrofars Date: Mon, 22 Apr 2024 12:32:45 -0300 Subject: [PATCH 28/30] chore: gitignore for nats data folder --- deploy/compose/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deploy/compose/.gitignore b/deploy/compose/.gitignore index 3b43ac1..af5be97 100644 --- a/deploy/compose/.gitignore +++ b/deploy/compose/.gitignore @@ -1,4 +1,6 @@ portainer_data/* !portainer_data/.gitkeep mongo_data/* -!mongo_data/.gitkeep \ No newline at end of file +!mongo_data/.gitkeep +nats_data/* +!nats_data/.gitkeep \ No newline at end of file From e8a2a3690ebec080f06961566a7b77069f288b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20Ant=C3=B4nio=20Farias=20Machado?= <83298718+leandrofars@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:46:25 -0300 Subject: [PATCH 29/30] docs(readme): kubernetes deployment info --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06c1202..c0e9104 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This repository aims to promote the development of a multi-vendor management pla
  • Infrastructure:

-![image](https://github.com/OktopUSP/oktopus/assets/83298718/67873f6c-d3db-4045-8569-7e1135fc5fa7) +![image](https://github.com/OktopUSP/oktopus/assets/83298718/aa6feb7f-ac32-465c-b166-aa6b3ee5b68a)
  • @@ -68,13 +68,15 @@ This repository aims to promote the development of a multi-vendor management pla
    • Quick start:

      -Run app using Docker: +Run app using Docker Compose:
       user@user-laptop:~$ cd oktopus/deploy/compose
       user@user-laptop:~/oktopus/deploy/compose$ COMPOSE_PROFILES=nats,controller,mqtt,stomp,ws,adapter,frontend,portainer docker compose up -d
       
      +Oktopus deployment in Kubernetes still is in beta phase: Instructions for Kubernetes deployment

      UI will open at port 3000: +
    • Device test agent (obuspa):

      From 03018af61e9668a15849e8edb0886b9fb01e00b0 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Mon, 22 Apr 2024 13:23:44 -0300 Subject: [PATCH 30/30] feat(acs): build and deployment --- backend/services/acs/build/Dockerfile | 8 +++ backend/services/acs/build/Makefile | 61 ++++++++++++++++++++++ backend/services/acs/cmd/{ => acs}/main.go | 0 build/Makefile | 1 + 4 files changed, 70 insertions(+) create mode 100644 backend/services/acs/build/Dockerfile create mode 100644 backend/services/acs/build/Makefile rename backend/services/acs/cmd/{ => acs}/main.go (100%) diff --git a/backend/services/acs/build/Dockerfile b/backend/services/acs/build/Dockerfile new file mode 100644 index 0000000..0e8119a --- /dev/null +++ b/backend/services/acs/build/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.22.2@sha256:450e3822c7a135e1463cd83e51c8e2eb03b86a02113c89424e6f0f8344bb4168 as builder +WORKDIR /app +COPY ../ . +RUN CGO_ENABLED=0 GOOS=linux go build -o acs cmd/acs/main.go + +FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed +COPY --from=builder /app/acs / +ENTRYPOINT ["/acs"] \ No newline at end of file diff --git a/backend/services/acs/build/Makefile b/backend/services/acs/build/Makefile new file mode 100644 index 0000000..10c7fd2 --- /dev/null +++ b/backend/services/acs/build/Makefile @@ -0,0 +1,61 @@ +.PHONY: help build push start stop release remove delete run logs bash + +DOCKER_USER ?= oktopusp +DOCKER_APP ?= acs +DOCKER_TAG ?= $(shell git log --format="%h" -n 1) +CONTAINER_SHELL ?= /bin/sh + +.DEFAULT_GOAL := help + +help: + @echo "Makefile arguments:" + @echo "" + @echo "DOCKER_USER - docker user to build image" + @echo "DOCKER_APP - docker image name" + @echo "DOCKER_TAG - docker image tag" + @echo "CONTAINER_SHELL - container shell e.g:'/bin/bash'" + @echo "" + @echo "Makefile commands:" + @echo "" + @echo "build - docker image build" + @echo "push - push docker iamge to registry" + @echo "run - create and start docker container with the image" + @echo "start - start existent docker container with the image" + @echo "stop - stop docker container running the image" + @echo "remove - remove docker container running the image" + @echo "delete - delete docker image" + @echo "logs - show logs of docker container" + @echo "bash - access container shell" + @echo "release - tag image as latest and push to registry" + +build: + @docker build -t ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} -f Dockerfile ../ + +run: + @docker run -d --name ${DOCKER_USER}-${DOCKER_APP} ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + +stop: + @docker stop ${DOCKER_USER}-${DOCKER_APP} + +remove: stop + @docker rm ${DOCKER_USER}-${DOCKER_APP} + +delete: + @docker rmi ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + +start: + @docker start ${DOCKER_USER}-${DOCKER_APP} + +push: + @docker push ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + +logs: + @docker logs -f ${DOCKER_USER}-${DOCKER_APP} + +bash: + @docker exec -it ${DOCKER_USER}-${DOCKER_APP} ${CONTAINER_SHELL} + +release: build + @docker push ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + @docker tag ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} ${DOCKER_USER}/${DOCKER_APP}:latest + @docker push ${DOCKER_USER}/${DOCKER_APP}:latest \ No newline at end of file diff --git a/backend/services/acs/cmd/main.go b/backend/services/acs/cmd/acs/main.go similarity index 100% rename from backend/services/acs/cmd/main.go rename to backend/services/acs/cmd/acs/main.go diff --git a/build/Makefile b/build/Makefile index 4406994..a801e79 100644 --- a/build/Makefile +++ b/build/Makefile @@ -24,6 +24,7 @@ build: build-frontend build-backend build-backend: @make build -C ../backend/services/controller/build/ DOCKER_USER=${DOCKER_USER} + @make build -C ../backend/services/acs/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/utils/socketio/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/mtp/adapter/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/mtp/ws-adapter/build/ DOCKER_USER=${DOCKER_USER}