Create a different daemonset for gw running on controllers
diff --git a/charts/ovn/templates/bin/_ovn-controller-gw-init.sh.tpl b/charts/ovn/templates/bin/_ovn-controller-gw-init.sh.tpl
new file mode 100644
index 0000000..5528155
--- /dev/null
+++ b/charts/ovn/templates/bin/_ovn-controller-gw-init.sh.tpl
@@ -0,0 +1,142 @@
+#!/bin/bash -xe
+
+# Copyright 2023 VEXXHOST, Inc.
+#
+# 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.
+
+function get_ip_address_from_interface {
+  local interface=$1
+  local ip=$(ip -4 -o addr s "${interface}" | awk '{ print $4; exit }' | awk -F '/' '{print $1}')
+  if [ -z "${ip}" ] ; then
+    exit 1
+  fi
+  echo ${ip}
+}
+
+function get_ip_prefix_from_interface {
+  local interface=$1
+  local prefix=$(ip -4 -o addr s "${interface}" | awk '{ print $4; exit }' | awk -F '/' '{print $2}')
+  if [ -z "${prefix}" ] ; then
+    exit 1
+  fi
+  echo ${prefix}
+}
+
+function migrate_ip_from_nic {
+  src_nic=$1
+  bridge_name=$2
+
+  # Enabling explicit error handling: We must avoid to lose the IP
+  # address in the migration process. Hence, on every error, we
+  # attempt to assign the IP back to the original NIC and exit.
+  set +e
+
+  ip=$(get_ip_address_from_interface ${src_nic})
+  prefix=$(get_ip_prefix_from_interface ${src_nic})
+
+  bridge_ip=$(get_ip_address_from_interface "${bridge_name}")
+  bridge_prefix=$(get_ip_prefix_from_interface "${bridge_name}")
+
+  ip link set ${bridge_name} up
+
+  if [[ -n "${ip}" && -n "${prefix}" ]]; then
+    ip addr flush dev ${src_nic}
+    if [ $? -ne 0 ] ; then
+      ip addr add ${ip}/${prefix} dev ${src_nic}
+      echo "Error while flushing IP from ${src_nic}."
+      exit 1
+    fi
+
+    ip addr add ${ip}/${prefix} dev "${bridge_name}"
+    if [ $? -ne 0 ] ; then
+      echo "Error assigning IP to bridge "${bridge_name}"."
+      ip addr add ${ip}/${prefix} dev ${src_nic}
+      exit 1
+    fi
+  elif [[ -n "${bridge_ip}" && -n "${bridge_prefix}" ]]; then
+    echo "Bridge '${bridge_name}' already has IP assigned. Keeping the same:: IP:[${bridge_ip}]; Prefix:[${bridge_prefix}]..."
+  elif [[ -z "${bridge_ip}" && -z "${ip}" ]]; then
+    echo "Interface and bridge have no ips configured. Leaving as is."
+  else
+    echo "Interface ${name} has invalid IP address. IP:[${ip}]; Prefix:[${prefix}]..."
+    exit 1
+  fi
+
+  set -e
+}
+
+# Detect tunnel interface
+tunnel_interface="{{- .Values.network.interface.tunnel -}}"
+if [ -z "${tunnel_interface}" ] ; then
+    # search for interface with tunnel network routing
+    tunnel_network_cidr="{{- .Values.network.interface.tunnel_network_cidr -}}"
+    if [ -z "${tunnel_network_cidr}" ] ; then
+        tunnel_network_cidr="0/0"
+    fi
+    # If there is not tunnel network gateway, exit
+    tunnel_interface=$(ip -4 route list ${tunnel_network_cidr} | awk -F 'dev' '{ print $2; exit }' \
+        | awk '{ print $1 }') || exit 1
+fi
+ovs-vsctl set open . external_ids:ovn-encap-ip="$(get_ip_address_from_interface ${tunnel_interface})"
+
+# Configure system ID
+set +e
+ovs-vsctl get open . external-ids:system-id
+if [ $? -eq 1 ]; then
+  ovs-vsctl set open . external-ids:system-id="$(uuidgen)"
+fi
+set -e
+
+# Configure OVN remote
+{{- if empty .Values.conf.ovn_remote -}}
+{{- $sb_svc_name := "ovn-ovsdb-sb" -}}
+{{- $sb_svc := (tuple $sb_svc_name "internal" . | include "helm-toolkit.endpoints.hostname_fqdn_endpoint_lookup") -}}
+{{- $sb_port := (tuple "ovn-ovsdb-sb" "internal" "ovsdb" . | include "helm-toolkit.endpoints.endpoint_port_lookup") -}}
+{{- $sb_service_list := list -}}
+{{- range $i := until (.Values.pod.replicas.ovn_ovsdb_sb | int) -}}
+  {{- $sb_service_list = printf "tcp:%s-%d.%s:%s" $sb_svc_name $i $sb_svc $sb_port | append $sb_service_list -}}
+{{- end }}
+
+ovs-vsctl set open . external-ids:ovn-remote="{{ include "helm-toolkit.utils.joinListWithComma" $sb_service_list }}"
+{{- else -}}
+ovs-vsctl set open . external-ids:ovn-remote="{{ .Values.conf.ovn_remote }}"
+{{- end }}
+
+# Configure OVN values
+ovs-vsctl set open . external-ids:rundir="/var/run/openvswitch"
+ovs-vsctl set open . external-ids:ovn-encap-type="{{ .Values.conf.ovn_encap_type }}"
+ovs-vsctl set open . external-ids:ovn-bridge="{{ .Values.conf.ovn_bridge }}"
+ovs-vsctl set open . external-ids:ovn-bridge-mappings="{{ .Values.conf.ovn_bridge_mappings }}"
+ovs-vsctl set open . external-ids:ovn-cms-options="{{ .Values.conf.gw_ovn_cms_options }}"
+
+# Configure hostname
+{{- if .Values.conf.use_fqdn.compute }}
+  ovs-vsctl set open . external-ids:hostname="$(hostname -f)"
+{{- else }}
+  ovs-vsctl set open . external-ids:hostname="$(hostname)"
+{{- end }}
+
+# Create bridges and create ports
+# handle any bridge mappings
+# /tmp/auto_bridge_add is one line json file: {"br-ex1":"eth1","br-ex2":"eth2"}
+for bmap in `sed 's/[{}"]//g' /tmp/auto_bridge_add | tr "," "\n"`
+do
+  bridge=${bmap%:*}
+  iface=${bmap#*:}
+  ovs-vsctl --may-exist add-br $bridge -- set bridge $bridge protocols=OpenFlow13
+  if [ -n "$iface" ] && [ "$iface" != "null" ]
+  then
+    ovs-vsctl --may-exist add-port $bridge $iface
+    migrate_ip_from_nic $iface $bridge
+  fi
+done
diff --git a/charts/ovn/templates/configmap-bin.yaml b/charts/ovn/templates/configmap-bin.yaml
index a849dd8..1beb0d2 100644
--- a/charts/ovn/templates/configmap-bin.yaml
+++ b/charts/ovn/templates/configmap-bin.yaml
@@ -30,6 +30,8 @@
 {{ tuple "bin/_ovn-northd.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
   ovn-controller-init.sh: |
 {{ tuple "bin/_ovn-controller-init.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  ovn-controller-gw-init.sh: |
+{{ tuple "bin/_ovn-controller-gw-init.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
   ovn-controller.sh: |
 {{ tuple "bin/_ovn-controller.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
 {{- end }}
diff --git a/charts/ovn/templates/daemonset-controller-gw.yaml b/charts/ovn/templates/daemonset-controller-gw.yaml
new file mode 100644
index 0000000..6267970
--- /dev/null
+++ b/charts/ovn/templates/daemonset-controller-gw.yaml
@@ -0,0 +1,101 @@
+{{/*
+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.manifests.daemonset_controller }}
+{{- $envAll := . }}
+
+{{- $configMapName := "ovn-etc" }}
+{{- $serviceAccountName := "ovn-controller-gw" }}
+{{ tuple $envAll "ovn_controller_gw" $serviceAccountName | include "helm-toolkit.snippets.kubernetes_pod_rbac_serviceaccount" }}
+---
+kind: DaemonSet
+apiVersion: apps/v1
+metadata:
+  name: ovn-controller-gw
+  annotations:
+    {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" }}
+    configmap-bin-hash: {{ tuple "configmap-bin.yaml" . | include "helm-toolkit.utils.hash" }}
+  labels:
+{{ tuple $envAll "ovn" "ovn-controller-gw" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+spec:
+  selector:
+    matchLabels:
+{{ tuple $envAll "ovn" "ovn-controller-gw" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 6 }}
+  template:
+    metadata:
+      labels:
+{{ tuple $envAll "ovn" "ovn-controller-gw" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
+      annotations:
+{{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
+        configmap-bin-hash: {{ tuple "configmap-bin.yaml" . | include "helm-toolkit.utils.hash" }}
+        configmap-etc-hash: {{ tuple "configmap-etc.yaml" . | include "helm-toolkit.utils.hash" }}
+    spec:
+      serviceAccountName: {{ $serviceAccountName }}
+      hostNetwork: true
+      dnsPolicy: {{ .Values.pod.dns_policy }}
+      nodeSelector:
+        {{ .Values.labels.ovn_controller_gw.node_selector_key }}: {{ .Values.labels.ovn_controller_gw.node_selector_value }}
+      initContainers:
+{{- tuple $envAll "ovn_controller_gw" list | include "helm-toolkit.snippets.kubernetes_entrypoint_init_container" | indent 8 }}
+        - name: controller-init
+{{ tuple $envAll "ovn_controller_gw" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ dict "envAll" $envAll "application" "ovn_controller_gw" "container" "controller_init" | include "helm-toolkit.snippets.kubernetes_container_security_context" | indent 10 }}
+          command:
+            - /tmp/ovn-controller-gw-init.sh
+          volumeMounts:
+            - name: ovn-bin
+              mountPath: /tmp/ovn-controller-gw-init.sh
+              subPath: ovn-controller-gw-init.sh
+              readOnly: true
+            - name: run-openvswitch
+              mountPath: /run/openvswitch
+            - name: ovn-etc
+              mountPath: /tmp/auto_bridge_add
+              subPath: auto_bridge_add
+              readOnly: true
+      containers:
+        - name: controller
+{{ tuple $envAll "ovn_controller_gw" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.server | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+{{ dict "envAll" $envAll "application" "ovn_controller_gw" "container" "controller" | include "helm-toolkit.snippets.kubernetes_container_security_context" | indent 10 }}
+          command:
+            - /tmp/ovn-controller.sh
+            - start
+          lifecycle:
+            preStop:
+              exec:
+                command:
+                  - /tmp/ovn-controller.sh
+                  - stop
+          volumeMounts:
+            - name: ovn-bin
+              mountPath: /tmp/ovn-controller.sh
+              subPath: ovn-controller.sh
+              readOnly: true
+            - name: run-openvswitch
+              mountPath: /run/openvswitch
+      volumes:
+        - name: ovn-bin
+          configMap:
+            name: ovn-bin
+            defaultMode: 0777
+        - name: run-openvswitch
+          hostPath:
+            path: /run/openvswitch
+            type: DirectoryOrCreate
+        - name: ovn-etc
+          secret:
+            secretName: {{ $configMapName }}
+            defaultMode: 0444
+{{- end }}
diff --git a/charts/ovn/values.yaml b/charts/ovn/values.yaml
index 05408fa..41fca3e 100644
--- a/charts/ovn/values.yaml
+++ b/charts/ovn/values.yaml
@@ -24,6 +24,7 @@
     ovn_ovsdb_sb: docker.io/openstackhelm/ovn:latest-ubuntu_focal
     ovn_northd: docker.io/openstackhelm/ovn:latest-ubuntu_focal
     ovn_controller: docker.io/openstackhelm/ovn:latest-ubuntu_focal
+    ovn_controller_gw: 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
   pull_policy: "IfNotPresent"
@@ -44,7 +45,10 @@
     node_selector_key: openstack-network-node
     node_selector_value: enabled
   ovn_controller:
-    node_selector_key: openvswitch
+    node_selector_key: openstack-network-node
+    node_selector_value: enabled
+  ovn_controller_gw:
+    node_selector_key: openstack-control-plane
     node_selector_value: enabled
 
 volume:
@@ -66,7 +70,8 @@
     tunnel_network_cidr: "0/0"
 
 conf:
-  ovn_cms_options: "enable-chassis-as-gw,availability-zones=nova"
+  ovn_cms_options: "availability-zones=nova"
+  gw_ovn_cms_options: "enable-chassis-as-gw,availability-zones=nova"
   ovn_encap_type: geneve
   ovn_bridge: br-int
   ovn_bridge_mappings: external:br-ex
@@ -97,6 +102,15 @@
           capabilities:
             add:
               - SYS_NICE
+    ovn_controller_gw:
+      container:
+        controller_init:
+          privileged: true
+          runAsUser: 0
+        controller:
+          capabilities:
+            add:
+              - SYS_NICE
   tolerations:
     ovn_ovsdb_nb:
       enabled: false
@@ -106,6 +120,8 @@
       enabled: false
     ovn_controller:
       enabled: false
+    ovn_controller_gw:
+      enabled: false
   affinity:
     anti:
       type:
@@ -153,6 +169,10 @@
           enabled: true
           min_ready_seconds: 0
           max_unavailable: 1
+        ovn_controller_gw:
+          enabled: true
+          min_ready_seconds: 0
+          max_unavailable: 1
   resources:
     enabled: false
     ovs:
@@ -184,6 +204,13 @@
         limits:
           memory: "1024Mi"
           cpu: "2000m"
+      ovn_controller_gw:
+        requests:
+          memory: "128Mi"
+          cpu: "100m"
+        limits:
+          memory: "1024Mi"
+          cpu: "2000m"
     jobs:
       image_repo_sync:
         requests:
@@ -199,6 +226,7 @@
     ovn_ovsdb_sb: ovn-ovsdb-sb-oci-image-registry-key
     ovn_northd: ovn-northd-oci-image-registry-key
     ovn_controller: ovn-controller-oci-image-registry-key
+    ovn_controller_gw: ovn-controller-gw-oci-image-registry-key
 
 # TODO: Check these endpoints?!
 endpoints:
@@ -274,6 +302,9 @@
   ovn_controller:
     ingress:
       - {}
+  ovn_controller_gw:
+    ingress:
+      - {}
     egress:
       - {}
 
@@ -299,6 +330,10 @@
       services:
         - endpoint: internal
           service: ovn-ovsdb-sb
+    ovn_controller_gw:
+      services:
+        - endpoint: internal
+          service: ovn-ovsdb-sb
       pod:
         - requireSameNode: true
           labels:
@@ -320,5 +355,6 @@
   statefulset_ovn_ovsdb_sb: true
   deployment_ovn_northd: true
   daemonset_ovn_controller: true
+  daemonset_ovn_controller_gw: true
   job_image_repo_sync: true
 ...