feat: add ovn_network_logging_parser support (#1481)

depends-on: #1541
diff --git a/charts/ovn/templates/bin/_ovn-network-logging-parser.sh.tpl b/charts/ovn/templates/bin/_ovn-network-logging-parser.sh.tpl
new file mode 100644
index 0000000..06eaaa7
--- /dev/null
+++ b/charts/ovn/templates/bin/_ovn-network-logging-parser.sh.tpl
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+{{/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -ex
+COMMAND="${@:-start}"
+
+function start () {
+  exec uwsgi --ini /etc/neutron/neutron-ovn-network-logging-parser-uwsgi.ini
+}
+
+function stop () {
+  kill -TERM 1
+}
+
+$COMMAND
diff --git a/charts/ovn/templates/configmap-bin.yaml b/charts/ovn/templates/configmap-bin.yaml
index 82001f9..7754747 100644
--- a/charts/ovn/templates/configmap-bin.yaml
+++ b/charts/ovn/templates/configmap-bin.yaml
@@ -26,4 +26,6 @@
 {{- end }}
   ovn-controller-init.sh: |
 {{ tuple "bin/_ovn-controller-init.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  ovn-network-logging-parser.sh: |
+{{ tuple "bin/_ovn-network-logging-parser.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
 {{- end }}
diff --git a/charts/ovn/templates/configmap-etc.yaml b/charts/ovn/templates/configmap-etc.yaml
index 47b84be..0d221f1 100644
--- a/charts/ovn/templates/configmap-etc.yaml
+++ b/charts/ovn/templates/configmap-etc.yaml
@@ -17,6 +17,12 @@
 {{- $envAll := index . 1 }}
 {{- with $envAll }}
 
+{{- if empty (index .Values.conf.ovn_network_logging_parser_uwsgi.uwsgi "http-socket") -}}
+{{- $http_socket_port := tuple "ovn_logging_parser" "service" "api" . | include "helm-toolkit.endpoints.endpoint_port_lookup" | toString }}
+{{- $http_socket := printf "0.0.0.0:%s" $http_socket_port }}
+{{- $_ := set .Values.conf.ovn_network_logging_parser_uwsgi.uwsgi "http-socket" $http_socket -}}
+{{- end -}}
+
 ---
 apiVersion: v1
 kind: Secret
@@ -25,7 +31,7 @@
 type: Opaque
 data:
   auto_bridge_add: {{ toJson $envAll.Values.conf.auto_bridge_add | b64enc }}
-
+  neutron-ovn-network-logging-parser-uwsgi.ini: {{ include "helm-toolkit.utils.to_oslo_conf" .Values.conf.ovn_network_logging_parser_uwsgi | b64enc }}
 {{- end }}
 {{- end }}
 
diff --git a/charts/ovn/templates/daemonset-controller-gw.yaml b/charts/ovn/templates/daemonset-controller-gw.yaml
index eb309c5..3ecd81d 100644
--- a/charts/ovn/templates/daemonset-controller-gw.yaml
+++ b/charts/ovn/templates/daemonset-controller-gw.yaml
@@ -112,6 +112,54 @@
               mountPath: /var/run/ovn
             - name: run-openvswitch
               mountPath: /var/run/openvswitch
+            - name: shared
+              mountPath: /var/log/ovn/
+        {{- if .Values.pod.sidecars.vector }}
+        - name: vector
+{{ tuple $envAll "vector" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.ovn_controller_gw.vector | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+{{ dict "envAll" $envAll "application" "ovn_controller_gw" "container" "vector" | include "helm-toolkit.snippets.kubernetes_container_security_context" | indent 10 }}
+          command:
+            - vector
+            - --config
+            - /etc/vector/vector.toml
+          volumeMounts:
+            - name: vector-config
+              mountPath: /etc/vector
+            - name: shared
+              mountPath: /logs
+            - name: vector-data
+              mountPath: /var/lib/vector
+        {{- end }}
+        {{- if .Values.pod.sidecars.ovn_logging_parser }}
+        - name: log-parser
+{{ tuple $envAll "ovn_logging_parser" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.ovn_controller_gw.ovn_logging_parser | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+{{ dict "envAll" $envAll "application" "ovn_controller_gw" "container" "ovn_logging_parser" | include "helm-toolkit.snippets.kubernetes_container_security_context" | indent 10 }}
+          command:
+            - /tmp/ovn-network-logging-parser.sh
+            - start
+          env:
+            - name: VECTOR_HTTP_ENDPOINT
+              value: http://localhost:5001
+          ports:
+            - name: http
+              containerPort: {{ tuple "ovn_logging_parser" "service" "api" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+              protocol: TCP
+          volumeMounts:
+            - name: neutron-etc
+              mountPath: /etc/neutron/neutron.conf
+              subPath: neutron.conf
+              readOnly: true
+            - name: ovn-bin
+              mountPath: /tmp/ovn-network-logging-parser.sh
+              subPath: ovn-network-logging-parser.sh
+              readOnly: true
+            - name: ovn-etc
+              mountPath: /etc/neutron/neutron-ovn-network-logging-parser-uwsgi.ini
+              subPath: neutron-ovn-network-logging-parser-uwsgi.ini
+              readOnly: true
+        {{- end }}
       volumes:
         - name: ovn-bin
           configMap:
@@ -125,4 +173,19 @@
           secret:
             secretName: {{ $configMapName }}
             defaultMode: 0444
+        - name: shared
+          emptyDir: {}
+        {{- if .Values.pod.sidecars.vector }}
+        - name: vector-config
+          secret:
+            secretName: ovn-vector-config
+        - name: vector-data
+          emptyDir: {}
+        {{- end }}
+        {{- if .Values.pod.sidecars.ovn_logging_parser }}
+        - name: neutron-etc
+          secret:
+            secretName: neutron-etc
+            defaultMode: 0444
+        {{- end }}
 {{- end }}
diff --git a/charts/ovn/templates/secret-vector.yaml b/charts/ovn/templates/secret-vector.yaml
new file mode 100644
index 0000000..028e8a9
--- /dev/null
+++ b/charts/ovn/templates/secret-vector.yaml
@@ -0,0 +1,56 @@
+{{/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.pod.sidecars.vector }}
+
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ovn-vector-config
+type: Opaque
+stringData:
+  vector.toml: |
+    [sources.file_logs]
+    type = "file"
+    include = [ "/logs/ovn-controller.log" ]
+
+    [sinks.ovn_log_parser_in]
+    type = "http"
+    inputs = ["file_logs"]
+    uri = "{{ tuple "ovn_logging_parser" "default" "api" . | include "helm-toolkit.endpoints.keystone_endpoint_uri_lookup" }}"
+    encoding.codec = "json"
+    method = "post"
+
+    [sources.ovn_log_parser_out]
+    type = "http_server"
+    address = "0.0.0.0:5001"
+    encoding = "json"
+
+    [transforms.parse_log_message]
+    type = "remap"
+    inputs = ["ovn_log_parser_out"]
+    source = '''
+      del(.source_type)
+      del(.path)
+    '''
+
+    [sinks.loki_sink]
+    type = "loki"
+    labels.event_source = "network_logs"
+    inputs = ["parse_log_message"]
+    endpoint = "http://loki.monitoring:3100"
+    encoding.codec = "json"
+    tenant_id = "{{`{{ project_id }}`}}"
+{{- end }}
diff --git a/charts/ovn/values.yaml b/charts/ovn/values.yaml
index 214dd16..d0f2406 100644
--- a/charts/ovn/values.yaml
+++ b/charts/ovn/values.yaml
@@ -26,6 +26,8 @@
     ovn_controller: docker.io/openstackhelm/ovn:latest-ubuntu_focal
     dep_check: quay.io/airshipit/kubernetes-entrypoint:v1.0.0
     image_repo_sync: docker.io/library/docker:17.07.0
+    vector: docker.io/timberio/vector:0.39.0-debian
+    ovn_logging_parser: docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy
   pull_policy: "IfNotPresent"
   local_registry:
     active: false
@@ -86,6 +88,24 @@
   use_fqdn:
     compute: true
 
+  ovn_network_logging_parser_uwsgi:
+    uwsgi:
+      add-header: "Connection: close"
+      buffer-size: 65535
+      die-on-term: true
+      enable-threads: true
+      exit-on-reload: false
+      hook-master-start: unix_signal:15 gracefully_kill_them_all
+      lazy-apps: true
+      log-x-forwarded-for: true
+      master: true
+      processes: 1
+      procname-prefix-spaced: "neutron-ovn-network-logging-parser:"
+      route-user-agent: '^kube-probe.* donotlog:'
+      thunder-lock: true
+      worker-reload-mercy: 80
+      wsgi-file: /var/lib/openstack/bin/neutron-ovn-network-logging-parser-wsgi
+
 pod:
   security_context:
     ovn_northd:
@@ -112,6 +132,12 @@
           capabilities:
             add:
               - SYS_NICE
+        ovn_logging_parser:
+          allowPrivilegeEscalation: false
+          readOnlyRootFilesystem: true
+        vector:
+          allowPrivilegeEscalation: false
+          readOnlyRootFilesystem: true
   tolerations:
     ovn_ovsdb_nb:
       enabled: false
@@ -240,6 +266,21 @@
         limits:
           memory: "1024Mi"
           cpu: "2000m"
+    ovn_controller_gw:
+      ovn_logging_parser:
+        requests:
+          memory: "128Mi"
+          cpu: "100m"
+        limits:
+          memory: "256Mi"
+          cpu: "500m"
+      vector:
+        requests:
+          memory: "128Mi"
+          cpu: "100m"
+        limits:
+          memory: "256Mi"
+          cpu: "500m"
     jobs:
       image_repo_sync:
         requests:
@@ -248,6 +289,9 @@
         limits:
           memory: "1024Mi"
           cpu: "2000m"
+  sidecars:
+    ovn_logging_parser: false
+    vector: false
 
 secrets:
   oci_image_registry:
@@ -311,6 +355,22 @@
         default: 6642
       raft:
         default: 6644
+  ovn_logging_parser:
+    name: ovn-logging-parser
+    namespace: null
+    hosts:
+      default: localhost
+    host_fqdn_override:
+      default: localhost
+    scheme:
+      default: 'http'
+      service: 'http'
+    path:
+      default: "/logs"
+    port:
+      api:
+        default: 9697
+        service: 9697
 
 network_policy:
   ovn_ovsdb_nb:
diff --git a/charts/patches/ovn/0002-add-logging-parser.patch b/charts/patches/ovn/0002-add-logging-parser.patch
new file mode 100644
index 0000000..f964369
--- /dev/null
+++ b/charts/patches/ovn/0002-add-logging-parser.patch
@@ -0,0 +1,395 @@
+diff --git a/ovn/templates/bin/_ovn-network-logging-parser.sh.tpl b/ovn/templates/bin/_ovn-network-logging-parser.sh.tpl
+new file mode 100644
+index 00000000..06eaaa7f
+--- /dev/null
++++ b/ovn/templates/bin/_ovn-network-logging-parser.sh.tpl
+@@ -0,0 +1,28 @@
++#!/bin/bash
++
++{{/*
++Licensed under the Apache License, Version 2.0 (the "License");
++you may not use this file except in compliance with the License.
++You may obtain a copy of the License at
++
++   http://www.apache.org/licenses/LICENSE-2.0
++
++Unless required by applicable law or agreed to in writing, software
++distributed under the License is distributed on an "AS IS" BASIS,
++WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++See the License for the specific language governing permissions and
++limitations under the License.
++*/}}
++
++set -ex
++COMMAND="${@:-start}"
++
++function start () {
++  exec uwsgi --ini /etc/neutron/neutron-ovn-network-logging-parser-uwsgi.ini
++}
++
++function stop () {
++  kill -TERM 1
++}
++
++$COMMAND
+diff --git a/ovn/templates/configmap-bin.yaml b/ovn/templates/configmap-bin.yaml
+index 82001f99..77547470 100644
+--- a/ovn/templates/configmap-bin.yaml
++++ b/ovn/templates/configmap-bin.yaml
+@@ -26,4 +26,6 @@ data:
+ {{- end }}
+   ovn-controller-init.sh: |
+ {{ tuple "bin/_ovn-controller-init.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
++  ovn-network-logging-parser.sh: |
++{{ tuple "bin/_ovn-network-logging-parser.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+ {{- end }}
+diff --git a/ovn/templates/configmap-etc.yaml b/ovn/templates/configmap-etc.yaml
+index 47b84be8..0d221f19 100644
+--- a/ovn/templates/configmap-etc.yaml
++++ b/ovn/templates/configmap-etc.yaml
+@@ -17,6 +17,12 @@ limitations under the License.
+ {{- $envAll := index . 1 }}
+ {{- with $envAll }}
+ 
++{{- if empty (index .Values.conf.ovn_network_logging_parser_uwsgi.uwsgi "http-socket") -}}
++{{- $http_socket_port := tuple "ovn_logging_parser" "service" "api" . | include "helm-toolkit.endpoints.endpoint_port_lookup" | toString }}
++{{- $http_socket := printf "0.0.0.0:%s" $http_socket_port }}
++{{- $_ := set .Values.conf.ovn_network_logging_parser_uwsgi.uwsgi "http-socket" $http_socket -}}
++{{- end -}}
++
+ ---
+ apiVersion: v1
+ kind: Secret
+@@ -25,7 +31,7 @@ metadata:
+ type: Opaque
+ data:
+   auto_bridge_add: {{ toJson $envAll.Values.conf.auto_bridge_add | b64enc }}
+-
++  neutron-ovn-network-logging-parser-uwsgi.ini: {{ include "helm-toolkit.utils.to_oslo_conf" .Values.conf.ovn_network_logging_parser_uwsgi | b64enc }}
+ {{- end }}
+ {{- end }}
+ 
+diff --git a/ovn/templates/daemonset-controller-gw.yaml b/ovn/templates/daemonset-controller-gw.yaml
+index eb309c5e..3ecd81dc 100644
+--- a/ovn/templates/daemonset-controller-gw.yaml
++++ b/ovn/templates/daemonset-controller-gw.yaml
+@@ -112,6 +112,54 @@ spec:
+               mountPath: /var/run/ovn
+             - name: run-openvswitch
+               mountPath: /var/run/openvswitch
++            - name: shared
++              mountPath: /var/log/ovn/
++        {{- if .Values.pod.sidecars.vector }}
++        - name: vector
++{{ tuple $envAll "vector" | include "helm-toolkit.snippets.image" | indent 10 }}
++{{ tuple $envAll $envAll.Values.pod.resources.ovn_controller_gw.vector | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
++{{ dict "envAll" $envAll "application" "ovn_controller_gw" "container" "vector" | include "helm-toolkit.snippets.kubernetes_container_security_context" | indent 10 }}
++          command:
++            - vector
++            - --config
++            - /etc/vector/vector.toml
++          volumeMounts:
++            - name: vector-config
++              mountPath: /etc/vector
++            - name: shared
++              mountPath: /logs
++            - name: vector-data
++              mountPath: /var/lib/vector
++        {{- end }}
++        {{- if .Values.pod.sidecars.ovn_logging_parser }}
++        - name: log-parser
++{{ tuple $envAll "ovn_logging_parser" | include "helm-toolkit.snippets.image" | indent 10 }}
++{{ tuple $envAll $envAll.Values.pod.resources.ovn_controller_gw.ovn_logging_parser | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
++{{ dict "envAll" $envAll "application" "ovn_controller_gw" "container" "ovn_logging_parser" | include "helm-toolkit.snippets.kubernetes_container_security_context" | indent 10 }}
++          command:
++            - /tmp/ovn-network-logging-parser.sh
++            - start
++          env:
++            - name: VECTOR_HTTP_ENDPOINT
++              value: http://localhost:5001
++          ports:
++            - name: http
++              containerPort: {{ tuple "ovn_logging_parser" "service" "api" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
++              protocol: TCP
++          volumeMounts:
++            - name: neutron-etc
++              mountPath: /etc/neutron/neutron.conf
++              subPath: neutron.conf
++              readOnly: true
++            - name: ovn-bin
++              mountPath: /tmp/ovn-network-logging-parser.sh
++              subPath: ovn-network-logging-parser.sh
++              readOnly: true
++            - name: ovn-etc
++              mountPath: /etc/neutron/neutron-ovn-network-logging-parser-uwsgi.ini
++              subPath: neutron-ovn-network-logging-parser-uwsgi.ini
++              readOnly: true
++        {{- end }}
+       volumes:
+         - name: ovn-bin
+           configMap:
+@@ -125,4 +173,19 @@ spec:
+           secret:
+             secretName: {{ $configMapName }}
+             defaultMode: 0444
++        - name: shared
++          emptyDir: {}
++        {{- if .Values.pod.sidecars.vector }}
++        - name: vector-config
++          secret:
++            secretName: ovn-vector-config
++        - name: vector-data
++          emptyDir: {}
++        {{- end }}
++        {{- if .Values.pod.sidecars.ovn_logging_parser }}
++        - name: neutron-etc
++          secret:
++            secretName: neutron-etc
++            defaultMode: 0444
++        {{- end }}
+ {{- end }}
+diff --git a/ovn/templates/secret-vector.yaml b/ovn/templates/secret-vector.yaml
+new file mode 100644
+index 00000000..028e8a9a
+--- /dev/null
++++ b/ovn/templates/secret-vector.yaml
+@@ -0,0 +1,56 @@
++{{/*
++Licensed under the Apache License, Version 2.0 (the "License");
++you may not use this file except in compliance with the License.
++You may obtain a copy of the License at
++
++   http://www.apache.org/licenses/LICENSE-2.0
++
++Unless required by applicable law or agreed to in writing, software
++distributed under the License is distributed on an "AS IS" BASIS,
++WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++See the License for the specific language governing permissions and
++limitations under the License.
++*/}}
++
++{{- if .Values.pod.sidecars.vector }}
++
++---
++apiVersion: v1
++kind: Secret
++metadata:
++  name: ovn-vector-config
++type: Opaque
++stringData:
++  vector.toml: |
++    [sources.file_logs]
++    type = "file"
++    include = [ "/logs/ovn-controller.log" ]
++
++    [sinks.ovn_log_parser_in]
++    type = "http"
++    inputs = ["file_logs"]
++    uri = "{{ tuple "ovn_logging_parser" "default" "api" . | include "helm-toolkit.endpoints.keystone_endpoint_uri_lookup" }}"
++    encoding.codec = "json"
++    method = "post"
++
++    [sources.ovn_log_parser_out]
++    type = "http_server"
++    address = "0.0.0.0:5001"
++    encoding = "json"
++
++    [transforms.parse_log_message]
++    type = "remap"
++    inputs = ["ovn_log_parser_out"]
++    source = '''
++      del(.source_type)
++      del(.path)
++    '''
++
++    [sinks.loki_sink]
++    type = "loki"
++    labels.event_source = "network_logs"
++    inputs = ["parse_log_message"]
++    endpoint = "http://loki.monitoring:3100"
++    encoding.codec = "json"
++    tenant_id = "{{`{{ project_id }}`}}"
++{{- end }}
+diff --git a/ovn/values.yaml b/ovn/values.yaml
+index 214dd16f..d0f2406b 100644
+--- a/ovn/values.yaml
++++ b/ovn/values.yaml
+@@ -26,6 +26,8 @@ images:
+     ovn_controller: docker.io/openstackhelm/ovn:latest-ubuntu_focal
+     dep_check: quay.io/airshipit/kubernetes-entrypoint:v1.0.0
+     image_repo_sync: docker.io/library/docker:17.07.0
++    vector: docker.io/timberio/vector:0.39.0-debian
++    ovn_logging_parser: docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy
+   pull_policy: "IfNotPresent"
+   local_registry:
+     active: false
+@@ -86,6 +88,24 @@ conf:
+   use_fqdn:
+     compute: true
+ 
++  ovn_network_logging_parser_uwsgi:
++    uwsgi:
++      add-header: "Connection: close"
++      buffer-size: 65535
++      die-on-term: true
++      enable-threads: true
++      exit-on-reload: false
++      hook-master-start: unix_signal:15 gracefully_kill_them_all
++      lazy-apps: true
++      log-x-forwarded-for: true
++      master: true
++      processes: 1
++      procname-prefix-spaced: "neutron-ovn-network-logging-parser:"
++      route-user-agent: '^kube-probe.* donotlog:'
++      thunder-lock: true
++      worker-reload-mercy: 80
++      wsgi-file: /var/lib/openstack/bin/neutron-ovn-network-logging-parser-wsgi
++
+ pod:
+   security_context:
+     ovn_northd:
+@@ -112,6 +132,12 @@ pod:
+           capabilities:
+             add:
+               - SYS_NICE
++        ovn_logging_parser:
++          allowPrivilegeEscalation: false
++          readOnlyRootFilesystem: true
++        vector:
++          allowPrivilegeEscalation: false
++          readOnlyRootFilesystem: true
+   tolerations:
+     ovn_ovsdb_nb:
+       enabled: false
+@@ -240,6 +266,21 @@ pod:
+         limits:
+           memory: "1024Mi"
+           cpu: "2000m"
++    ovn_controller_gw:
++      ovn_logging_parser:
++        requests:
++          memory: "128Mi"
++          cpu: "100m"
++        limits:
++          memory: "256Mi"
++          cpu: "500m"
++      vector:
++        requests:
++          memory: "128Mi"
++          cpu: "100m"
++        limits:
++          memory: "256Mi"
++          cpu: "500m"
+     jobs:
+       image_repo_sync:
+         requests:
+@@ -248,6 +289,9 @@ pod:
+         limits:
+           memory: "1024Mi"
+           cpu: "2000m"
++  sidecars:
++    ovn_logging_parser: false
++    vector: false
+ 
+ secrets:
+   oci_image_registry:
+@@ -311,6 +355,22 @@ endpoints:
+         default: 6642
+       raft:
+         default: 6644
++  ovn_logging_parser:
++    name: ovn-logging-parser
++    namespace: null
++    hosts:
++      default: localhost
++    host_fqdn_override:
++      default: localhost
++    scheme:
++      default: 'http'
++      service: 'http'
++    path:
++      default: "/logs"
++    port:
++      api:
++        default: 9697
++        service: 9697
+ 
+ network_policy:
+   ovn_ovsdb_nb:
+diff --git a/images/neutron/Dockerfile b/images/neutron/Dockerfile
+index 7032319b..992091b6 100644
+--- a/images/neutron/Dockerfile
++++ b/images/neutron/Dockerfile
+@@ -13,12 +13,16 @@ RUN git -C /src/neutron-vpnaas fetch --unshallow
+ ARG POLICY_SERVER_GIT_REF=4a86b140d5510823a8fb8a59137feddf5b111b26
+ ADD --keep-git-dir=true https://github.com/vexxhost/neutron-policy-server.git#${POLICY_SERVER_GIT_REF} /src/neutron-policy-server
+ RUN git -C /src/neutron-policy-server fetch --unshallow
++ARG LOG_PASER_GIT_REF=3bc113d9fc0eb3264feca5900e550f6ed15503c2
++ADD --keep-git-dir=true https://github.com/vexxhost/neutron-ovn-network-logging-parser.git#${LOG_PASER_GIT_REF} /src/neutron-ovn-network-logging-parser
++RUN git -C /src/neutron-ovn-network-logging-parser fetch --unshallow
+ RUN --mount=type=cache,mode=0755,target=/root/.cache/pip,sharing=private <<EOF bash -xe
+ pip3 install \
+     --constraint /upper-constraints.txt \
+         /src/neutron \
+         /src/neutron-vpnaas \
+-        /src/neutron-policy-server
++        /src/neutron-policy-server \
++        /src/neutron-ovn-network-logging-parser
+ EOF
+ 
+ FROM registry.atmosphere.dev/library/openstack-python-runtime:${RELEASE}
+diff --git a/roles/defaults/vars/main.yml b/roles/defaults/vars/main.yml
+index ff60d44a..2904e8de 100644
+--- a/roles/defaults/vars/main.yml
++++ b/roles/defaults/vars/main.yml
+@@ -173,6 +173,7 @@ _atmosphere_images:
+   openvswitch_db_server: "registry.atmosphere.dev/library/openvswitch:{{ atmosphere_release }}"
+   openvswitch_vswitchd: "registry.atmosphere.dev/library/openvswitch:{{ atmosphere_release }}"
+   ovn_controller: "registry.atmosphere.dev/library/ovn-host:{{ atmosphere_release }}"
++  ovn_logging_parser: "registry.atmosphere.dev/library/neutron:{{ atmosphere_release }}"
+   ovn_northd: "registry.atmosphere.dev/library/ovn-central:{{ atmosphere_release }}"
+   ovn_ovsdb_nb: "registry.atmosphere.dev/library/ovn-central:{{ atmosphere_release }}"
+   ovn_ovsdb_sb: "registry.atmosphere.dev/library/ovn-central:{{ atmosphere_release }}"
+diff --git a/roles/neutron/vars/main.yml b/roles/neutron/vars/main.yml
+index fc587502..5c814499 100644
+--- a/roles/neutron/vars/main.yml
++++ b/roles/neutron/vars/main.yml
+@@ -70,6 +70,8 @@ __neutron_helm_values:
+         metadata_proxy_shared_secret: "{{ openstack_helm_endpoints['compute_metadata']['secret'] }}"
+     plugins:
+       ml2_conf:
++        agent:
++          extensions: "log"
+         ml2:
+           extension_drivers: dns_domain_ports,port_security,qos
+           type_drivers: flat,gre,vlan,vxlan
+@@ -85,7 +87,7 @@ __neutron_ovn_helm_values:
+   conf:
+     neutron:
+       DEFAULT:
+-        service_plugins: qos,ovn-router,segments,trunk
++        service_plugins: qos,ovn-router,segments,trunk,log
+       ovn:
+         ovn_emit_need_to_frag: true
+     ovn_metadata_agent:
+diff --git a/roles/ovn/defaults/main.yml b/roles/ovn/defaults/main.yml
+index b9045986..a6ebdb96 100644
+--- a/roles/ovn/defaults/main.yml
++++ b/roles/ovn/defaults/main.yml
+@@ -18,3 +18,5 @@ ovn_helm_chart_ref: /usr/local/src/ovn
+ 
+ ovn_helm_release_namespace: openstack
+ ovn_helm_values: {}
++
++ovn_network_logging_parser_enabled: true
+diff --git a/roles/ovn/vars/main.yml b/roles/ovn/vars/main.yml
+index 59d9c209..603b4edf 100644
+--- a/roles/ovn/vars/main.yml
++++ b/roles/ovn/vars/main.yml
+@@ -35,3 +35,6 @@ _ovn_helm_values:
+       ovn_ovsdb_nb: 3
+       ovn_ovsdb_sb: 3
+       ovn_northd: 3
++    sidecars:
++      ovn_logging_parser: "{{ ovn_network_logging_parser_enabled }}"
++      vector: "{{ ovn_network_logging_parser_enabled }}"
diff --git a/images/neutron/Dockerfile b/images/neutron/Dockerfile
index 7032319..992091b 100644
--- a/images/neutron/Dockerfile
+++ b/images/neutron/Dockerfile
@@ -13,12 +13,16 @@
 ARG POLICY_SERVER_GIT_REF=4a86b140d5510823a8fb8a59137feddf5b111b26
 ADD --keep-git-dir=true https://github.com/vexxhost/neutron-policy-server.git#${POLICY_SERVER_GIT_REF} /src/neutron-policy-server
 RUN git -C /src/neutron-policy-server fetch --unshallow
+ARG LOG_PASER_GIT_REF=3bc113d9fc0eb3264feca5900e550f6ed15503c2
+ADD --keep-git-dir=true https://github.com/vexxhost/neutron-ovn-network-logging-parser.git#${LOG_PASER_GIT_REF} /src/neutron-ovn-network-logging-parser
+RUN git -C /src/neutron-ovn-network-logging-parser fetch --unshallow
 RUN --mount=type=cache,mode=0755,target=/root/.cache/pip,sharing=private <<EOF bash -xe
 pip3 install \
     --constraint /upper-constraints.txt \
         /src/neutron \
         /src/neutron-vpnaas \
-        /src/neutron-policy-server
+        /src/neutron-policy-server \
+        /src/neutron-ovn-network-logging-parser
 EOF
 
 FROM registry.atmosphere.dev/library/openstack-python-runtime:${RELEASE}
diff --git a/roles/defaults/vars/main.yml b/roles/defaults/vars/main.yml
index ff60d44..2904e8d 100644
--- a/roles/defaults/vars/main.yml
+++ b/roles/defaults/vars/main.yml
@@ -173,6 +173,7 @@
   openvswitch_db_server: "registry.atmosphere.dev/library/openvswitch:{{ atmosphere_release }}"
   openvswitch_vswitchd: "registry.atmosphere.dev/library/openvswitch:{{ atmosphere_release }}"
   ovn_controller: "registry.atmosphere.dev/library/ovn-host:{{ atmosphere_release }}"
+  ovn_logging_parser: "registry.atmosphere.dev/library/neutron:{{ atmosphere_release }}"
   ovn_northd: "registry.atmosphere.dev/library/ovn-central:{{ atmosphere_release }}"
   ovn_ovsdb_nb: "registry.atmosphere.dev/library/ovn-central:{{ atmosphere_release }}"
   ovn_ovsdb_sb: "registry.atmosphere.dev/library/ovn-central:{{ atmosphere_release }}"
diff --git a/roles/neutron/vars/main.yml b/roles/neutron/vars/main.yml
index fc58750..ed551e0 100644
--- a/roles/neutron/vars/main.yml
+++ b/roles/neutron/vars/main.yml
@@ -85,7 +85,7 @@
   conf:
     neutron:
       DEFAULT:
-        service_plugins: qos,ovn-router,segments,trunk
+        service_plugins: qos,ovn-router,segments,trunk,log
       ovn:
         ovn_emit_need_to_frag: true
     ovn_metadata_agent:
@@ -93,6 +93,8 @@
         metadata_proxy_shared_secret: "{{ openstack_helm_endpoints['compute_metadata']['secret'] }}"
     plugins:
       ml2_conf:
+        agent:
+          extensions: "log"
         ml2:
           type_drivers: flat,vlan,geneve
           tenant_network_types: geneve
diff --git a/roles/ovn/defaults/main.yml b/roles/ovn/defaults/main.yml
index b904598..a6ebdb9 100644
--- a/roles/ovn/defaults/main.yml
+++ b/roles/ovn/defaults/main.yml
@@ -18,3 +18,5 @@
 
 ovn_helm_release_namespace: openstack
 ovn_helm_values: {}
+
+ovn_network_logging_parser_enabled: true
diff --git a/roles/ovn/vars/main.yml b/roles/ovn/vars/main.yml
index 59d9c20..603b4ed 100644
--- a/roles/ovn/vars/main.yml
+++ b/roles/ovn/vars/main.yml
@@ -35,3 +35,6 @@
       ovn_ovsdb_nb: 3
       ovn_ovsdb_sb: 3
       ovn_northd: 3
+    sidecars:
+      ovn_logging_parser: "{{ ovn_network_logging_parser_enabled }}"
+      vector: "{{ ovn_network_logging_parser_enabled }}"