Added OpenStack services

Change-Id: I9aadad2919a6a7400d6f11a884317e33e787b416
diff --git a/roles/ceph_csi_rbd/defaults/main.yml b/roles/ceph_csi_rbd/defaults/main.yml
new file mode 100644
index 0000000..fc16f2d
--- /dev/null
+++ b/roles/ceph_csi_rbd/defaults/main.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2022 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.
+
+ceph_csi_rbd_ceph_fsid: "{{ ceph_mon_fsid }}"
+ceph_csi_rbd_mons_group: ceph_mons
+
+ceph_csi_rbd_id: kube
+ceph_csi_rbd_user: "client.{{ ceph_csi_rbd_id }}"
+ceph_csi_rbd_pool: kube
diff --git a/roles/ceph_csi_rbd/meta/main.yml b/roles/ceph_csi_rbd/meta/main.yml
new file mode 100644
index 0000000..b3836f8
--- /dev/null
+++ b/roles/ceph_csi_rbd/meta/main.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: ceph
+      helm_repository_repo_url: https://ceph.github.io/csi-charts
+  - cilium
diff --git a/roles/ceph_csi_rbd/tasks/main.yml b/roles/ceph_csi_rbd/tasks/main.yml
new file mode 100644
index 0000000..43e8498
--- /dev/null
+++ b/roles/ceph_csi_rbd/tasks/main.yml
@@ -0,0 +1,72 @@
+# Copyright (c) 2022 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.
+
+- name: collect facts for all monitors
+  run_once: true
+  delegate_to: "{{ item }}"
+  delegate_facts: true
+  ansible.builtin.setup:
+    gather_subset: network
+  loop: "{{ groups[ceph_csi_rbd_mons_group] }}"
+
+- vexxhost.atmosphere.ceph_pool:
+    name: "{{ ceph_csi_rbd_pool }}"
+    application: rbd
+    pg_autoscale_mode: on
+
+- vexxhost.atmosphere.ceph_key:
+    name: "{{ ceph_csi_rbd_user }}"
+    caps:
+      mon: profile rbd
+      mgr: profile rbd pool={{ ceph_csi_rbd_pool }}
+      osd: profile rbd pool={{ ceph_csi_rbd_pool }}
+
+- vexxhost.atmosphere.ceph_key:
+    name: "{{ ceph_csi_rbd_user }}"
+    state: info
+    output_format: json
+  register: _ceph_key
+
+- ansible.builtin.set_fact:
+    _ceph_rbd_csi_ceph_keyring: "{{ _ceph_key.stdout | from_json | first }}"
+
+- kubernetes.core.helm:
+    name: ceph-csi-rbd
+    chart_ref: ceph/ceph-csi-rbd
+    chart_version: 3.5.1
+    release_namespace: kube-system
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      csiConfig:
+        - clusterID: "{{ ceph_mon_fsid }}"
+          monitors: "{{ groups[ceph_csi_rbd_mons_group] | map('extract', hostvars, ['ansible_default_ipv4', 'address']) }}"
+      nodeplugin:
+        httpMetrics:
+          containerPort: 8081
+      provisioner:
+        nodeSelector:
+          openstack-control-plane: enabled
+      storageClass:
+        create: true
+        name: general
+        annotations:
+          storageclass.kubernetes.io/is-default-class: "true"
+        clusterID: "{{ ceph_csi_rbd_ceph_fsid }}"
+        pool: "{{ ceph_csi_rbd_pool }}"
+        mountOptions:
+          - discard
+      secret:
+        create: true
+        userID: "{{ ceph_csi_rbd_id }}"
+        userKey: "{{ _ceph_rbd_csi_ceph_keyring.key }}"
diff --git a/roles/cert_manager/defaults/main.yml b/roles/cert_manager/defaults/main.yml
new file mode 100644
index 0000000..45415d9
--- /dev/null
+++ b/roles/cert_manager/defaults/main.yml
@@ -0,0 +1,26 @@
+# Copyright (c) 2022 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.
+
+cert_manager_acme_server: https://acme-v02.api.letsencrypt.org/directory
+
+cert_manager_issuer:
+  acme:
+    email: mnaser@vexxhost.com
+    server: "{{ cert_manager_acme_server }}"
+    privateKeySecretRef:
+      name: issuer-account-key
+    solvers:
+      - http01:
+          ingress:
+            class: openstack
diff --git a/roles/cert_manager/meta/main.yml b/roles/cert_manager/meta/main.yml
new file mode 100644
index 0000000..1cf4a6e
--- /dev/null
+++ b/roles/cert_manager/meta/main.yml
@@ -0,0 +1,22 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: jetstack
+      helm_repository_repo_url: https://charts.jetstack.io
+  - cilium
+  - ingress_nginx
+  - openstack_namespace
diff --git a/roles/cert_manager/tasks/main.yml b/roles/cert_manager/tasks/main.yml
new file mode 100644
index 0000000..b06b4f6
--- /dev/null
+++ b/roles/cert_manager/tasks/main.yml
@@ -0,0 +1,43 @@
+# Copyright (c) 2022 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.
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: cert-manager
+    chart_ref: jetstack/cert-manager
+    chart_version: v1.7.1
+    release_namespace: cert-manager
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      installCRDs: true
+      volumes:
+        - name: etc-ssl-certs
+          hostPath:
+            path: /etc/ssl/certs
+      volumeMounts:
+        - name: etc-ssl-certs
+          mountPath: /etc/ssl/certs
+          readOnly: true
+
+- name: Create issuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: cert-manager.io/v1
+      kind: Issuer
+      metadata:
+        name: openstack
+        namespace: openstack
+      spec: "{{ cert_manager_issuer }}"
diff --git a/roles/cilium/meta/main.yml b/roles/cilium/meta/main.yml
new file mode 100644
index 0000000..fda3c18
--- /dev/null
+++ b/roles/cilium/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: cilium
+      helm_repository_repo_url: https://helm.cilium.io/
diff --git a/roles/cilium/tasks/main.yml b/roles/cilium/tasks/main.yml
new file mode 100644
index 0000000..98b5bb9
--- /dev/null
+++ b/roles/cilium/tasks/main.yml
@@ -0,0 +1,21 @@
+# Copyright (c) 2022 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.
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: cilium
+    chart_ref: cilium/cilium
+    chart_version: 1.10.7
+    release_namespace: kube-system
+    kubeconfig: /etc/kubernetes/admin.conf
diff --git a/roles/helm_diff/tasks/main.yml b/roles/helm_diff/tasks/main.yml
new file mode 100644
index 0000000..909e650
--- /dev/null
+++ b/roles/helm_diff/tasks/main.yml
@@ -0,0 +1,27 @@
+# Copyright (c) 2022 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.
+
+- name: Retrieve values for current Helm release
+  kubernetes.core.helm_info:
+    name: "{{ helm_diff_release_name }}"
+    release_namespace: "{{ helm_diff_release_namespace }}"
+  register: _helm_diff_info
+
+- name: Generate diff between old and new values
+  ansible.utils.fact_diff:
+    before: "{{ _helm_diff_info.status['values'] }}"
+    after: "{{ helm_diff_values }}"
+
+- name: Pause until you can verify values are correct
+  ansible.builtin.pause:
diff --git a/roles/helm_repository/meta/main.yml b/roles/helm_repository/meta/main.yml
new file mode 100644
index 0000000..05e1c2d
--- /dev/null
+++ b/roles/helm_repository/meta/main.yml
@@ -0,0 +1,16 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - helm
diff --git a/roles/helm_repository/tasks/main.yml b/roles/helm_repository/tasks/main.yml
new file mode 100644
index 0000000..a7dd7e2
--- /dev/null
+++ b/roles/helm_repository/tasks/main.yml
@@ -0,0 +1,18 @@
+# Copyright (c) 2022 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.
+
+- name: Configure Helm repository ({{ helm_repository_name }})
+  kubernetes.core.helm_repository:
+    name: "{{ helm_repository_name }}"
+    repo_url: "{{ helm_repository_repo_url }}"
diff --git a/roles/ingress_nginx/meta/main.yml b/roles/ingress_nginx/meta/main.yml
new file mode 100644
index 0000000..bde6443
--- /dev/null
+++ b/roles/ingress_nginx/meta/main.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: ingress-nginx
+      helm_repository_repo_url: https://kubernetes.github.io/ingress-nginx
+  - openstack_namespace
diff --git a/roles/ingress_nginx/tasks/main.yml b/roles/ingress_nginx/tasks/main.yml
new file mode 100644
index 0000000..6436383
--- /dev/null
+++ b/roles/ingress_nginx/tasks/main.yml
@@ -0,0 +1,39 @@
+# Copyright (c) 2022 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.
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: ingress-nginx
+    chart_ref: ingress-nginx/ingress-nginx
+    chart_version: 4.0.17
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      controller:
+        config:
+          proxy-buffer-size: 16k
+        dnsPolicy: ClusterFirstWithHostNet
+        hostNetwork: true
+        ingressClassResource:
+          name: openstack
+        ingressClass: openstack
+        extraArgs:
+          default-ssl-certificate: ingress-nginx/wildcard
+        kind: DaemonSet
+        nodeSelector:
+          openstack-control-plane: enabled
+        service:
+          type: ClusterIP
+        admissionWebhooks:
+          port: 7443
diff --git a/roles/kube_prometheus_stack/meta/main.yml b/roles/kube_prometheus_stack/meta/main.yml
new file mode 100644
index 0000000..137f7b5
--- /dev/null
+++ b/roles/kube_prometheus_stack/meta/main.yml
@@ -0,0 +1,21 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: prometheus-community
+      helm_repository_repo_url: https://prometheus-community.github.io/helm-charts
+  - cilium
+  - ceph_csi_rbd
diff --git a/roles/kube_prometheus_stack/tasks/main.yml b/roles/kube_prometheus_stack/tasks/main.yml
new file mode 100644
index 0000000..22c3e4d
--- /dev/null
+++ b/roles/kube_prometheus_stack/tasks/main.yml
@@ -0,0 +1,270 @@
+# Copyright (c) 2022 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.
+
+- ansible.builtin.slurp:
+    src: /etc/kubernetes/pki/etcd/ca.crt
+  register: _etcd_ca_crt
+
+- ansible.builtin.slurp:
+    src: /etc/kubernetes/pki/etcd/healthcheck-client.crt
+  register: _etcd_healthcheck_client_crt
+
+- ansible.builtin.slurp:
+    src: /etc/kubernetes/pki/etcd/healthcheck-client.key
+  register: _etcd_healthcheck_client_key
+
+- kubernetes.core.helm:
+    name: kube-prometheus-stack
+    chart_ref: prometheus-community/kube-prometheus-stack
+    chart_version: 30.2.0
+    release_namespace: monitoring
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      alertmanager:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      grafana:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      kubeApiServer:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_node_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      kubelet:
+        serviceMonitor:
+          cAdvisorRelabelings:
+            - sourceLabels: [__metrics_path__]
+              targetLabel: metrics_path
+            - sourceLabels: ["node"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|node|service)$"
+          probesRelabelings:
+            - sourceLabels: [__metrics_path__]
+              targetLabel: metrics_path
+            - sourceLabels: ["node"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|node|service)$"
+          relabelings:
+            - sourceLabels: [__metrics_path__]
+              targetLabel: metrics_path
+            - sourceLabels: ["node"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|node|service)$"
+      kubeControllerManager:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_node_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      coreDns:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_name"]
+              targetLabel: "instance"
+            - regex: "^(container|endpoint|namespace|pod|service)$"
+              action: "labeldrop"
+      kubeEtcd:
+        serviceMonitor:
+          scheme: https
+          serverName: localhost
+          insecureSkipVerify: false
+          caFile: /etc/prometheus/secrets/kube-prometheus-stack-etcd-client-cert/ca.crt
+          certFile: /etc/prometheus/secrets/kube-prometheus-stack-etcd-client-cert/healthcheck-client.crt
+          keyFile: /etc/prometheus/secrets/kube-prometheus-stack-etcd-client-cert/healthcheck-client.key
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_node_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      kubeScheduler:
+        service:
+          port: 10259
+          targetPort: 10259
+        serviceMonitor:
+          https: true
+          insecureSkipVerify: true
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_node_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      kubeProxy:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_node_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+      kube-state-metrics:
+        prometheus:
+          monitor:
+            relabelings:
+              - sourceLabels: ["__meta_kubernetes_pod_name"]
+                targetLabel: "instance"
+              - action: "labeldrop"
+                regex: "^(container|endpoint|namespace|pod|service)$"
+        nodeSelector:
+          openstack-control-plane: enabled
+      prometheus:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+        prometheusSpec:
+          nodeSelector:
+            openstack-control-plane: enabled
+          secrets:
+            - kube-prometheus-stack-etcd-client-cert
+      prometheusOperator:
+        serviceMonitor:
+          relabelings:
+            - sourceLabels: ["__meta_kubernetes_pod_name"]
+              targetLabel: "instance"
+            - action: "labeldrop"
+              regex: "^(container|endpoint|namespace|pod|service)$"
+        nodeSelector:
+          openstack-control-plane: enabled
+      prometheus-node-exporter:
+        extraArgs:
+          - --collector.diskstats.ignored-devices=^(ram|loop|nbd|fd|(h|s|v|xv)d[a-z]|nvme\\d+n\\d+p)\\d+$
+          - --collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|fuse.squashfuse_ll|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$
+          - --collector.filesystem.mount-points-exclude=^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+|var/lib/kubelet/pods/.+|var/lib/kubelet/plugins/kubernetes.io/csi/.+|run/containerd/.+)($|/)
+          - --collector.netclass.ignored-devices=^(lxc|cilium_|qbr|qvb|qvo|ovs-system).*$
+          - --collector.netdev.device-exclude=^(lxc|cilium_|qbr|qvb|qvo|ovs-system).*$
+        prometheus:
+          monitor:
+            relabelings:
+              - sourceLabels: ["__meta_kubernetes_pod_node_name"]
+                targetLabel: "instance"
+              - action: "labeldrop"
+                regex: "^(container|endpoint|namespace|pod|service)$"
+      additionalPrometheusRulesMap:
+        coredns:
+          groups:
+            - name: coredns
+              rules:
+                - alert: CoreDNSDown
+                  expr: absent(up{job="coredns"} == 1)
+                  for: 15m
+                  labels:
+                    severity: critical
+                - alert: CoreDNSLatencyHigh
+                  expr: histogram_quantile(0.99, sum(rate(coredns_dns_request_duration_seconds_bucket{job="coredns"}[5m])) by(server, zone, le)) > 4
+                  for: 10m
+                  labels:
+                    severity: critical
+                - alert: CoreDNSErrorsHigh
+                  expr: sum(rate(coredns_dns_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_dns_responses_total{job="coredns"}[5m])) > 0.01
+                  for: 10m
+                  labels:
+                    severity: warning
+                - alert: CoreDNSErrorsHigh
+                  expr: sum(rate(coredns_dns_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_dns_responses_total{job="coredns"}[5m])) > 0.03
+                  for: 10m
+                  labels:
+                    severity: critical
+            - name: coredns_forward
+              rules:
+                - alert: CoreDNSForwardLatencyHigh
+                  expr: histogram_quantile(0.99, sum(rate(coredns_forward_request_duration_seconds_bucket{job="coredns"}[5m])) by(to, le)) > 4
+                  for: 10m
+                  labels:
+                    severity: critical
+                - alert: CoreDNSForwardErrorsHigh
+                  expr: sum(rate(coredns_forward_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_forward_responses_total{job="coredns"}[5m])) > 0.01
+                  for: 10m
+                  labels:
+                    severity: warning
+                - alert: CoreDNSForwardErrorsHigh
+                  expr: sum(rate(coredns_forward_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_forward_responses_total{job="coredns"}[5m])) > 0.03
+                  for: 10m
+                  labels:
+                    severity: critical
+                - alert: CoreDNSForwardHealthcheckFailureCount
+                  expr: sum(rate(coredns_forward_healthcheck_failures_total{job="coredns"}[5m])) by (to) > 0
+                  for: 2m
+                  labels:
+                    severity: warning
+                - alert: CoreDNSForwardHealthcheckBrokenCount
+                  expr: sum(rate(coredns_forward_healthcheck_broken_total{job="coredns"}[5m])) > 0
+                  for: 2m
+                  labels:
+                    severity: critical
+        node-exporter-local:
+          groups:
+            - name: node
+              rules:
+                - alert: NodeHighLoadAverage
+                  expr: node_load5 / count(node_cpu_seconds_total{mode="system"}) without (cpu, mode) > 1.5
+                  for: 30m
+                  labels:
+                    severity: warning
+                - alert: NodeHighMemoryUsage
+                  expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 2.5
+                  for: 2m
+                  labels:
+                    severity: critical
+                - alert: NodeHighCpuUsage
+                  expr: sum by(instance)(irate(node_cpu_seconds_total{mode='idle'}[5m])) < 1
+                  for: 2m
+                  labels:
+                    severity: warning
+                - alert: NodeLowEntropy
+                  expr: node_entropy_available_bits < 1000
+                  for: 5m
+                  labels:
+                    severity: warning
+            - name: softnet
+              rules:
+                - alert: NodeSoftNetTimesSqueezed
+                  expr: sum(rate(node_softnet_times_squeezed_total[1m])) by (instance) > 10
+                  for: 10m
+                  labels:
+                    severity: warning
+                - alert: NodeSoftNetDrops
+                  expr: sum(rate(node_softnet_dropped_total[1m])) by (instance) != 0
+                  for: 1m
+                  labels:
+                    severity: critical
+
+- kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Secret
+      metadata:
+        name: kube-prometheus-stack-etcd-client-cert
+        namespace: monitoring
+      data:
+        ca.crt: "{{ _etcd_ca_crt.content }}"
+        healthcheck-client.crt: "{{ _etcd_healthcheck_client_crt.content }}"
+        healthcheck-client.key: "{{ _etcd_healthcheck_client_key.content }}"
diff --git a/roles/kubernetes/templates/haproxy.cfg.j2 b/roles/kubernetes/templates/haproxy.cfg.j2
index b4cb838..053ce9a 100644
--- a/roles/kubernetes/templates/haproxy.cfg.j2
+++ b/roles/kubernetes/templates/haproxy.cfg.j2
@@ -47,5 +47,5 @@
     option ssl-hello-chk
     balance     roundrobin
 {% for host in groups[kubernetes_control_plane_group] %}
-        server {{ host }} {{ hostvars[host]['ansible_host'] }}:16443 check
+        server {{ host }} {{ hostvars[host]['ansible_default_ipv4']['address'] }}:16443 check
 {% endfor %}
diff --git a/roles/kubernetes/templates/kubeadm.yaml.j2 b/roles/kubernetes/templates/kubeadm.yaml.j2
index eba1d7c..cc30dfd 100644
--- a/roles/kubernetes/templates/kubeadm.yaml.j2
+++ b/roles/kubernetes/templates/kubeadm.yaml.j2
@@ -7,7 +7,7 @@
   kubeletExtraArgs:
     cgroups-per-qos: "false"
     enforce-node-allocatable: ""
-    node-ip: "{{ ansible_host }}"
+    node-ip: "{{ ansible_default_ipv4.address }}"
 ---
 apiVersion: kubeadm.k8s.io/v1beta3
 kind: JoinConfiguration
@@ -15,7 +15,7 @@
   kubeletExtraArgs:
     cgroups-per-qos: "false"
     enforce-node-allocatable: ""
-    node-ip: "{{ ansible_host }}"
+    node-ip: "{{ ansible_default_ipv4.address }}"
 {% if (_kubernetes_bootstrap_node is not defined) or (_kubernetes_bootstrap_node is defined and inventory_hostname != _kubernetes_bootstrap_node) %}
 discovery:
   bootstrapToken:
diff --git a/roles/node_feature_discovery/meta/main.yml b/roles/node_feature_discovery/meta/main.yml
new file mode 100644
index 0000000..503ce14
--- /dev/null
+++ b/roles/node_feature_discovery/meta/main.yml
@@ -0,0 +1,21 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: node-feature-discovery
+      helm_repository_repo_url: https://kubernetes-sigs.github.io/node-feature-discovery/charts
+  - cilium
+  - kube_prometheus_stack
diff --git a/roles/node_feature_discovery/tasks/main.yml b/roles/node_feature_discovery/tasks/main.yml
new file mode 100644
index 0000000..213c72f
--- /dev/null
+++ b/roles/node_feature_discovery/tasks/main.yml
@@ -0,0 +1,39 @@
+# Copyright (c) 2022 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.
+
+- kubernetes.core.helm:
+    name: node-feature-discovery
+    chart_ref: node-feature-discovery/node-feature-discovery
+    chart_version: 0.10.0
+    release_namespace: monitoring
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      image:
+        repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}/node-feature-discovery"
+        tag: 0.10.0
+      master:
+        nodeSelector:
+          openstack-control-plane: enabled
+      worker:
+        config:
+          sources:
+            custom:
+              - name: ipmi
+                labels:
+                  ipmi: "true"
+                matchFeatures:
+                  - feature: kernel.loadedmodule
+                    matchExpresions:
+                      ipmi_msghandler:
+                        op: Exists
diff --git a/roles/openstack_cli/defaults/main.yml b/roles/openstack_cli/defaults/main.yml
new file mode 100644
index 0000000..8079a74
--- /dev/null
+++ b/roles/openstack_cli/defaults/main.yml
@@ -0,0 +1,16 @@
+# Copyright (c) 2022 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.
+
+openstack_cli_packages:
+  - python3-openstackclient
diff --git a/roles/openstack_cli/tasks/main.yml b/roles/openstack_cli/tasks/main.yml
new file mode 100644
index 0000000..b1c032a
--- /dev/null
+++ b/roles/openstack_cli/tasks/main.yml
@@ -0,0 +1,33 @@
+# Copyright (c) 2022 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.
+
+- name: Install OpenStack client
+  become: true
+  ansible.builtin.apt:
+    name: "{{ openstack_cli_packages }}"
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_list: ["identity"]
+
+- name: Generate openrc file
+  become: true
+  ansible.builtin.template:
+    src: openrc.j2
+    dest: /root/openrc
+    owner: root
+    group: root
+    mode: 0600
diff --git a/roles/openstack_cli/templates/openrc.j2 b/roles/openstack_cli/templates/openrc.j2
new file mode 100644
index 0000000..7b56e0f
--- /dev/null
+++ b/roles/openstack_cli/templates/openrc.j2
@@ -0,0 +1,12 @@
+# {{ ansible_managed }}
+
+export OS_IDENTITY_API_VERSION=3
+
+export OS_AUTH_URL="{{ openstack_helm_endpoints['identity']['scheme']['public'] }}://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}/v3"
+export OS_AUTH_TYPE=password
+export OS_REGION_NAME="{{ openstack_helm_endpoints['identity']['auth']['admin']['region_name'] }}"
+export OS_USER_DOMAIN_NAME=Default
+export OS_USERNAME="{{ openstack_helm_endpoints['identity']['auth']['admin']['username'] }}"
+export OS_PASSWORD="{{ openstack_helm_endpoints['identity']['auth']['admin']['password'] }}"
+export OS_PROJECT_DOMAIN_NAME=Default
+export OS_PROJECT_NAME=admin
\ No newline at end of file
diff --git a/roles/openstack_helm_cinder/defaults/main.yml b/roles/openstack_helm_cinder/defaults/main.yml
new file mode 100644
index 0000000..02e5a98
--- /dev/null
+++ b/roles/openstack_helm_cinder/defaults/main.yml
@@ -0,0 +1,23 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_cinder_chart_repo_name: openstack-helm
+openstack_helm_cinder_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_cinder_chart_name: cinder
+
+openstack_helm_cinder_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_cinder_image_tag: 18.1.1.dev29-1
+openstack_helm_cinder_heat_image_tag: wallaby
+
+openstack_helm_cinder_values: {}
diff --git a/roles/openstack_helm_cinder/meta/main.yml b/roles/openstack_helm_cinder/meta/main.yml
new file mode 100644
index 0000000..8ed7540
--- /dev/null
+++ b/roles/openstack_helm_cinder/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_cinder_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_cinder_chart_repo_url }}"
diff --git a/roles/openstack_helm_cinder/tasks/main.yml b/roles/openstack_helm_cinder/tasks/main.yml
new file mode 100644
index 0000000..67fa685
--- /dev/null
+++ b/roles/openstack_helm_cinder/tasks/main.yml
@@ -0,0 +1,41 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_cinder_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_cinder_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_cinder_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_cinder_chart_name }}"
+    chart_ref: "{{ openstack_helm_cinder_chart_repo_name }}/{{ openstack_helm_cinder_chart_name }}"
+    chart_version: 0.2.15
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_cinder_values | combine(openstack_helm_cinder_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: volumev3
+    openstack_helm_ingress_service_name: cinder-api
+    openstack_helm_ingress_service_port: 8776
+    openstack_helm_ingress_annotations:
+      nginx.ingress.kubernetes.io/proxy-body-size: "0"
+      nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
diff --git a/roles/openstack_helm_cinder/vars/main.yml b/roles/openstack_helm_cinder/vars/main.yml
new file mode 100644
index 0000000..2b7fef5
--- /dev/null
+++ b/roles/openstack_helm_cinder/vars/main.yml
@@ -0,0 +1,61 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_cinder_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_cinder_image_repository }}/heat:{{ openstack_helm_cinder_heat_image_tag }}"
+      cinder_api: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_backup_storage_init: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_backup: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_db_sync: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_scheduler: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_storage_init: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_volume_usage_audit: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      cinder_volume: "{{ openstack_helm_cinder_image_repository }}/cinder:{{ openstack_helm_cinder_image_tag }}"
+      db_drop: "{{ openstack_helm_cinder_image_repository }}/heat:{{ openstack_helm_cinder_heat_image_tag }}"
+      db_init: "{{ openstack_helm_cinder_image_repository }}/heat:{{ openstack_helm_cinder_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_cinder_image_repository }}/kubernetes-entrypoint:latest"
+      ks_endpoints: "{{ openstack_helm_cinder_image_repository }}/heat:{{ openstack_helm_cinder_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_cinder_image_repository }}/heat:{{ openstack_helm_cinder_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_cinder_image_repository }}/heat:{{ openstack_helm_cinder_heat_image_tag }}"
+      rabbit_init: "{{ openstack_helm_cinder_image_repository }}/rabbitmq:3.8.23-management"
+  pod:
+    replicas:
+      api: 3
+      scheduler: 3
+  conf:
+    paste:
+      composite:openstack_volume_api_v3:
+        use: call:cinder.api.middleware.auth:pipeline_factory
+        noauth: cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler noauth apiv3
+        keystone: cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv3
+        keystone_nolimit: cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv3
+    cinder:
+      DEFAULT:
+        allowed_direct_url_schemes: cinder
+        backup_driver: cinder.backup.drivers.ceph.CephBackupDriver
+        log_config_append: null
+        os_region_name: "{{ openstack_helm_endpoints['identity']['auth']['cinder']['region_name'] }}"
+        volume_usage_audit_period: hour
+        volume_name_template: volume-%s
+      barbican:
+        barbican_endpoint_type: internal
+      cors:
+        allowed_origins: "*"
+  manifests:
+    ingress_api: false
+    job_clean: false
+    service_ingress_api: false
diff --git a/roles/openstack_helm_endpoints/defaults/main.yml b/roles/openstack_helm_endpoints/defaults/main.yml
new file mode 100644
index 0000000..0338286
--- /dev/null
+++ b/roles/openstack_helm_endpoints/defaults/main.yml
@@ -0,0 +1,102 @@
+# Copyright (c) 2022 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.
+
+# Base configuration
+openstack_helm_endpoints_region_name: "{{ undef(hint='You must specify an OpenStack region name') }}"
+openstack_helm_endpoints_config: {}
+
+# RabbitMQ
+openstack_helm_endpoints_rabbitmq_erlang_cookie: "{{ undef(hint='You must specify an RabbitMQ Erlang cookie') }}"
+openstack_helm_endpoints_rabbitmq_admin_password: "{{ undef(hint='You must specify a RabbitMQ admin password') }}"
+
+# Memcached
+openstack_helm_endpoints_memcached_secret_key: "{{ undef(hint='You must specify a Memcached secret key') }}"
+
+# Keystone
+openstack_helm_endpoints_keystone_api_host: "{{ undef(hint='You must specify a Keystone API hostname') }}"
+openstack_helm_endpoints_keystone_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_keystone_admin_password: "{{ undef(hint='You must specify a Keystone administrator password') }}"
+openstack_helm_endpoints_keystone_mariadb_password: "{{ undef(hint='You must specify a Keystone MariaDB password') }}"
+openstack_helm_endpoints_keystone_rabbitmq_password: "{{ undef(hint='You must specify a Keystone RabbitMQ password') }}"
+
+# Glance
+openstack_helm_endpoints_glance_api_host: "{{ undef(hint='You must specify a Glance API hostname') }}"
+openstack_helm_endpoints_glance_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_glance_keystone_password: "{{ undef(hint='You must specify a Glance Keystone password') }}"
+openstack_helm_endpoints_glance_mariadb_password: "{{ undef(hint='You must specify a Glance MariaDB password') }}"
+openstack_helm_endpoints_glance_rabbitmq_password: "{{ undef(hint='You must specify a Glance RabbitMQ password') }}"
+
+# Cinder
+openstack_helm_endpoints_cinder_api_host: "{{ undef(hint='You must specify a Cinder API hostname') }}"
+openstack_helm_endpoints_cinder_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_cinder_keystone_password: "{{ undef(hint='You must specify a Cinder Keystone password') }}"
+openstack_helm_endpoints_cinder_mariadb_password: "{{ undef(hint='You must specify a Cinder MariaDB password') }}"
+openstack_helm_endpoints_cinder_rabbitmq_password: "{{ undef(hint='You must specify a Cinder RabbitMQ password') }}"
+
+# Placement
+openstack_helm_endpoints_placement_api_host: "{{ undef(hint='You must specify a Placement API hostname') }}"
+openstack_helm_endpoints_placement_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_placement_keystone_password: "{{ undef(hint='You must specify a Placement Keystone password') }}"
+openstack_helm_endpoints_placement_mariadb_password: "{{ undef(hint='You must specify a Placement MariaDB password') }}"
+
+# Neutron
+openstack_helm_endpoints_neutron_api_host: "{{ undef(hint='You must specify a Neutron API hostname') }}"
+openstack_helm_endpoints_neutron_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_neutron_keystone_password: "{{ undef(hint='You must specify a Neutron Keystone password') }}"
+openstack_helm_endpoints_neutron_mariadb_password: "{{ undef(hint='You must specify a Neutron MariaDB password') }}"
+openstack_helm_endpoints_neutron_rabbitmq_password: "{{ undef(hint='You must specify a Neutron RabbitMQ password') }}"
+openstack_helm_endpoints_neutron_metadata_secret: "{{ undef(hint='You must specify a Neutron metadata secret') }}"
+
+# Nova
+openstack_helm_endpoints_nova_api_host: "{{ undef(hint='You must specify a Nova API hostname') }}"
+openstack_helm_endpoints_nova_novnc_host: "{{ undef(hint='You must specify a Nova NoVNC hostname') }}"
+openstack_helm_endpoints_nova_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_nova_keystone_password: "{{ undef(hint='You must specify a Nova Keystone password') }}"
+openstack_helm_endpoints_nova_mariadb_password: "{{ undef(hint='You must specify a Nova MariaDB password') }}"
+openstack_helm_endpoints_nova_rabbitmq_password: "{{ undef(hint='You must specify a Nova RabbitMQ password') }}"
+
+# Ironic
+openstack_helm_endpoints_ironic_api_host: "{{ undef(hint='You must specify an Ironic API hostname') }}"
+openstack_helm_endpoints_ironic_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_ironic_keystone_password: "{{ undef(hint='You must specify an Ironic Keystone password') }}"
+openstack_helm_endpoints_ironic_mariadb_password: "{{ undef(hint='You must specify an Ironic MariaDB password') }}"
+openstack_helm_endpoints_ironic_rabbitmq_password: "{{ undef(hint='You must specify an Ironic RabbitMQ password') }}"
+
+# Designate
+openstack_helm_endpoints_designate_api_host: "{{ undef(hint='You must specify a Designate API hostname') }}"
+openstack_helm_endpoints_designate_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_designate_keystone_password: "{{ undef(hint='You must specify a Designate Keystone password') }}"
+openstack_helm_endpoints_designate_mariadb_password: "{{ undef(hint='You must specify a Designate MariaDB password') }}"
+openstack_helm_endpoints_designate_rabbitmq_password: "{{ undef(hint='You must specify a Designate RabbitMQ password') }}"
+
+# Octavia
+openstack_helm_endpoints_octavia_api_host: "{{ undef(hint='You must specify an Octavia API hostname') }}"
+openstack_helm_endpoints_octavia_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_octavia_keystone_password: "{{ undef(hint='You must specify an Octavia Keystone password') }}"
+openstack_helm_endpoints_octavia_mariadb_password: "{{ undef(hint='You must specify an Octavia MariaDB password') }}"
+openstack_helm_endpoints_octavia_rabbitmq_password: "{{ undef(hint='You must specify an Octavia RabbitMQ password') }}"
+
+# Heat
+openstack_helm_endpoints_heat_api_host: "{{ undef(hint='You must specify a Heat API hostname') }}"
+openstack_helm_endpoints_heat_region_name: "{{ openstack_helm_endpoints_region_name }}"
+openstack_helm_endpoints_heat_keystone_password: "{{ undef(hint='You must specify a Heat Keystone password') }}"
+openstack_helm_endpoints_heat_trustee_keystone_password: "{{ undef(hint='You must specify a Heat trustee Keystone password') }}"
+openstack_helm_endpoints_heat_stack_user_keystone_password: "{{ undef(hint='You must specify a Heat stack user Keystone password') }}"
+openstack_helm_endpoints_heat_mariadb_password: "{{ undef(hint='You must specify a Heat MariaDB password') }}"
+openstack_helm_endpoints_heat_rabbitmq_password: "{{ undef(hint='You must specify a Heat RabbitMQ password') }}"
+openstack_helm_endpoints_heat_cfn_api_host: "{{ undef(hint='You must specify a Heat CloudFormation API hostname') }}"
+
+# Horizon
+openstack_helm_endpoints_horizon_api_host: "{{ undef(hint='You must specify a Horizon API hostname') }}"
+openstack_helm_endpoints_horizon_mariadb_password: "{{ undef(hint='You must specify a Horizon MariaDB password') }}"
diff --git a/roles/openstack_helm_endpoints/tasks/main.yml b/roles/openstack_helm_endpoints/tasks/main.yml
new file mode 100644
index 0000000..a73b6f9
--- /dev/null
+++ b/roles/openstack_helm_endpoints/tasks/main.yml
@@ -0,0 +1,65 @@
+# Copyright (c) 2022 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.
+
+- name: Get Helm values if chart is provided
+  block:
+    - name: Get the default values for the Helm chart
+      ansible.builtin.shell: helm show values {{ openstack_helm_endpoints_repo_name }}/{{ openstack_helm_endpoints_chart }}
+      changed_when: false
+      register: _helm_show_values
+
+    - name: Retrieve list of all the needed endpoints
+      ansible.builtin.set_fact:
+        openstack_helm_endpoints_list: "{{ _helm_show_values.stdout | from_yaml | community.general.json_query('keys(endpoints)') | difference(_openstack_helm_endpoints_ignore) }}"
+  when:
+    - openstack_helm_endpoints_list is not defined or openstack_helm_endpoints_list == None
+
+# NOTE(mnaser): Since we deploy the database using the operator and we let it
+#               generate the root password, we look it up if the fact has not
+#               been cached from a previous run.
+- name: Append endpoints for "oslo_db"
+  block:
+    - name: Grab Percona XtraDB cluster secret
+      kubernetes.core.k8s_info:
+        api_version: v1
+        kind: Secret
+        name: percona-xtradb
+        namespace: openstack
+      register: _openstack_helm_endpoints_oslo_db_secret
+
+    - name: Cache fact with Percona XtraDB password
+      ansible.builtin.set_fact:
+        openstack_helm_endpoints_maridb_admin_password: "{{ _openstack_helm_endpoints_oslo_db_secret.resources[0]['data']['root'] | b64decode }}"
+  when:
+    - '"oslo_db" in openstack_helm_endpoints_list'
+    - openstack_helm_endpoints_maridb_admin_password is not defined
+
+- name: Reset value for OpenStack_Helm endpoints
+  ansible.builtin.set_fact:
+    openstack_helm_endpoints: "{{ openstack_helm_endpoints_config }}"
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.set_fact:
+    openstack_helm_endpoints: |
+      {{ openstack_helm_endpoints | combine(lookup('vars', '_openstack_helm_endpoints_' + service), recursive=True) }}
+  loop: "{{ openstack_helm_endpoints_list }}"
+  loop_control:
+    loop_var: service
+
+# NOTE(mnaser): Since we use `openstack_helm_endpoints_list` to ensure that we
+#               have a common entry for endpoints and stay DRY, we need to
+#               reset the fact so it works for follow-up requests.
+- name: Clean-up facts
+  ansible.builtin.set_fact:
+    openstack_helm_endpoints_list:
diff --git a/roles/openstack_helm_endpoints/vars/main.yml b/roles/openstack_helm_endpoints/vars/main.yml
new file mode 100644
index 0000000..eb46051
--- /dev/null
+++ b/roles/openstack_helm_endpoints/vars/main.yml
@@ -0,0 +1,378 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_endpoints_ignore:
+  - ceph_object_store
+  - cloudwatch
+  - cluster_domain_suffix
+  - compute_spice_proxy
+  - fluentd
+  - ingress
+  - kube_dns
+  - ldap
+  - local_image_registry
+  - monitoring
+  - object_store
+  - prometheus_rabbitmq_exporter
+
+_openstack_helm_endpoints_oslo_db:
+  oslo_db:
+    auth:
+      admin:
+        password: "{{ openstack_helm_endpoints_maridb_admin_password }}"
+    hosts:
+      default: percona-xtradb-haproxy
+
+_openstack_helm_endpoints_oslo_messaging:
+  oslo_messaging:
+    auth:
+      erlang_cookie: "{{ openstack_helm_endpoints_rabbitmq_erlang_cookie }}"
+      user:
+        password: "{{ openstack_helm_endpoints_rabbitmq_admin_password }}"
+      # NOTE(mnaser): The following is not actually used by the chart, however,
+      #               since we are actually doing dynamic lookups to generate
+      #               endpoints, we add it here.
+      admin:
+        password: "{{ openstack_helm_endpoints_rabbitmq_admin_password }}"
+    statefulset:
+      replicas: 3
+
+_openstack_helm_endpoints_oslo_cache:
+  oslo_cache:
+    auth:
+      memcache_secret_key: "{{ openstack_helm_endpoints_memcached_secret_key }}"
+
+_openstack_helm_endpoints_identity:
+  identity:
+    auth:
+      admin:
+        region_name: "{{ openstack_helm_endpoints_keystone_region_name }}"
+        username: "admin-{{ openstack_helm_endpoints_keystone_region_name }}"
+        password: "{{ openstack_helm_endpoints_keystone_admin_password }}"
+    hosts:
+      default: keystone-api
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_keystone_api_host }}"
+    port:
+      api:
+        default: 5000
+        public: 443
+  oslo_db:
+    auth:
+      keystone:
+        password: "{{ openstack_helm_endpoints_keystone_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      keystone:
+        password: "{{ openstack_helm_endpoints_keystone_rabbitmq_password }}"
+
+_openstack_helm_endpoints_image:
+  identity:
+    auth:
+      glance:
+        region_name: "{{ openstack_helm_endpoints_glance_region_name }}"
+        username: "glance-{{ openstack_helm_endpoints_glance_region_name }}"
+        password: "{{ openstack_helm_endpoints_glance_keystone_password }}"
+  image:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_glance_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      glance:
+        password: "{{ openstack_helm_endpoints_glance_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      glance:
+        password: "{{ openstack_helm_endpoints_glance_rabbitmq_password }}"
+
+_openstack_helm_endpoints_volumev3:
+  identity:
+    auth:
+      cinder:
+        region_name: "{{ openstack_helm_endpoints_cinder_region_name }}"
+        username: "cinder-{{ openstack_helm_endpoints_cinder_region_name }}"
+        password: "{{ openstack_helm_endpoints_cinder_keystone_password }}"
+  volumev3:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_cinder_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      cinder:
+        password: "{{ openstack_helm_endpoints_cinder_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      cinder:
+        password: "{{ openstack_helm_endpoints_cinder_rabbitmq_password }}"
+
+_openstack_helm_endpoints_placement:
+  identity:
+    auth:
+      placement:
+        region_name: "{{ openstack_helm_endpoints_placement_region_name }}"
+        username: "placement-{{ openstack_helm_endpoints_placement_region_name }}"
+        password: "{{ openstack_helm_endpoints_placement_keystone_password }}"
+  oslo_db:
+    auth:
+      placement:
+        password: "{{ openstack_helm_endpoints_placement_mariadb_password }}"
+  placement:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_placement_api_host }}"
+    port:
+      api:
+        public: 443
+
+_openstack_helm_endpoints_network:
+  identity:
+    auth:
+      neutron:
+        region_name: "{{ openstack_helm_endpoints_neutron_region_name }}"
+        username: "neutron-{{ openstack_helm_endpoints_neutron_region_name }}"
+        password: "{{ openstack_helm_endpoints_neutron_keystone_password }}"
+  network:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_neutron_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      neutron:
+        password: "{{ openstack_helm_endpoints_neutron_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      neutron:
+        password: "{{ openstack_helm_endpoints_neutron_rabbitmq_password }}"
+
+_openstack_helm_endpoints_compute:
+  identity:
+    auth:
+      nova:
+        region_name: "{{ openstack_helm_endpoints_nova_region_name }}"
+        username: "nova-{{ openstack_helm_endpoints_nova_region_name }}"
+        password: "{{ openstack_helm_endpoints_nova_keystone_password }}"
+  compute:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_nova_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      nova:
+        password: "{{ openstack_helm_endpoints_nova_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      nova:
+        password: "{{ openstack_helm_endpoints_nova_rabbitmq_password }}"
+
+_openstack_helm_endpoints_oslo_db_api:
+  oslo_db_api:
+    auth:
+      admin:
+        password: "{{ openstack_helm_endpoints_maridb_admin_password }}"
+      nova:
+        password: "{{ openstack_helm_endpoints_nova_mariadb_password }}"
+    hosts:
+      default: percona-xtradb-haproxy
+
+_openstack_helm_endpoints_oslo_db_cell0:
+  oslo_db_cell0:
+    auth:
+      admin:
+        password: "{{ openstack_helm_endpoints_maridb_admin_password }}"
+      nova:
+        password: "{{ openstack_helm_endpoints_nova_mariadb_password }}"
+    hosts:
+      default: percona-xtradb-haproxy
+
+_openstack_helm_endpoints_compute_metadata:
+  compute_metadata:
+    secret: "{{ openstack_helm_endpoints_neutron_metadata_secret }}"
+    hosts:
+      public: nova-metadata
+    port:
+      metadata:
+        public: 8775
+
+_openstack_helm_endpoints_compute_novnc_proxy:
+  compute_novnc_proxy:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_nova_novnc_host }}"
+    port:
+      novnc_proxy:
+        public: 443
+
+_openstack_helm_endpoints_baremetal:
+  identity:
+    auth:
+      ironic:
+        region_name: "{{ openstack_helm_endpoints_ironic_region_name }}"
+        username: "ironic-{{ openstack_helm_endpoints_ironic_region_name }}"
+        password: "{{ openstack_helm_endpoints_ironic_keystone_password }}"
+  baremetal:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_ironic_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      ironic:
+        password: "{{ openstack_helm_endpoints_ironic_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      ironic:
+        password: "{{ openstack_helm_endpoints_ironic_rabbitmq_password }}"
+
+_openstack_helm_endpoints_dns:
+  identity:
+    auth:
+      designate:
+        region_name: "{{ openstack_helm_endpoints_designate_region_name }}"
+        username: "desigante-{{ openstack_helm_endpoints_designate_region_name }}"
+        password: "{{ openstack_helm_endpoints_designate_keystone_password }}"
+  dns:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_designate_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      designate:
+        password: "{{ openstack_helm_endpoints_designate_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      designate:
+        password: "{{ openstack_helm_endpoints_designate_rabbitmq_password }}"
+
+_openstack_helm_endpoints_load_balancer:
+  identity:
+    auth:
+      octavia:
+        region_name: "{{ openstack_helm_endpoints_octavia_region_name }}"
+        username: "octavia-{{ openstack_helm_endpoints_octavia_region_name }}"
+        password: "{{ openstack_helm_endpoints_octavia_keystone_password }}"
+  load_balancer:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_octavia_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      octavia:
+        password: "{{ openstack_helm_endpoints_octavia_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      octavia:
+        password: "{{ openstack_helm_endpoints_octavia_rabbitmq_password }}"
+
+_openstack_helm_endpoints_cloudformation:
+  cloudformation:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_heat_cfn_api_host }}"
+    port:
+      api:
+        public: 443
+
+_openstack_helm_endpoints_orchestration:
+  identity:
+    auth:
+      heat:
+        region_name: "{{ openstack_helm_endpoints_heat_region_name }}"
+        username: "heat-{{ openstack_helm_endpoints_heat_region_name }}"
+        password: "{{ openstack_helm_endpoints_heat_keystone_password }}"
+      heat_trustee:
+        region_name: "{{ openstack_helm_endpoints_heat_region_name }}"
+        username: "heat-trustee-{{ openstack_helm_endpoints_heat_region_name }}"
+        password: "{{ openstack_helm_endpoints_heat_trustee_keystone_password }}"
+      heat_stack_user:
+        region_name: "{{ openstack_helm_endpoints_heat_region_name }}"
+        username: "heat-stack-user-{{ openstack_helm_endpoints_heat_region_name }}"
+        password: "{{ openstack_helm_endpoints_heat_stack_user_keystone_password }}"
+    path:
+      public: /v3
+  orchestration:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_heat_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      heat:
+        password: "{{ openstack_helm_endpoints_heat_mariadb_password }}"
+  oslo_messaging:
+    auth:
+      heat:
+        password: "{{ openstack_helm_endpoints_heat_rabbitmq_password }}"
+
+_openstack_helm_endpoints_dashboard:
+  dashboard:
+    scheme:
+      public: https
+    host_fqdn_override:
+      public:
+        host: "{{ openstack_helm_endpoints_horizon_api_host }}"
+    port:
+      api:
+        public: 443
+  oslo_db:
+    auth:
+      horizon:
+        password: "{{ openstack_helm_endpoints_horizon_mariadb_password }}"
diff --git a/roles/openstack_helm_glance/defaults/main.yml b/roles/openstack_helm_glance/defaults/main.yml
new file mode 100644
index 0000000..bfb6e83
--- /dev/null
+++ b/roles/openstack_helm_glance/defaults/main.yml
@@ -0,0 +1,23 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_glance_chart_repo_name: openstack-helm
+openstack_helm_glance_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_glance_chart_name: glance
+
+openstack_helm_glance_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_glance_image_tag: 22.1.1.dev2-1
+openstack_helm_glance_heat_image_tag: wallaby
+
+openstack_helm_glance_values: {}
diff --git a/roles/openstack_helm_glance/meta/main.yml b/roles/openstack_helm_glance/meta/main.yml
new file mode 100644
index 0000000..a7eaa64
--- /dev/null
+++ b/roles/openstack_helm_glance/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_glance_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_glance_chart_repo_url }}"
diff --git a/roles/openstack_helm_glance/tasks/main.yml b/roles/openstack_helm_glance/tasks/main.yml
new file mode 100644
index 0000000..0abdb87
--- /dev/null
+++ b/roles/openstack_helm_glance/tasks/main.yml
@@ -0,0 +1,41 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_glance_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_glance_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_glance_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_glance_chart_name }}"
+    chart_ref: "{{ openstack_helm_glance_chart_repo_name }}/{{ openstack_helm_glance_chart_name }}"
+    chart_version: 0.2.10
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_glance_values | combine(openstack_helm_glance_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: image
+    openstack_helm_ingress_service_name: glance-api
+    openstack_helm_ingress_service_port: 9292
+    openstack_helm_ingress_annotations:
+      nginx.ingress.kubernetes.io/proxy-body-size: "0"
+      nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
diff --git a/roles/openstack_helm_glance/vars/main.yml b/roles/openstack_helm_glance/vars/main.yml
new file mode 100644
index 0000000..41b8016
--- /dev/null
+++ b/roles/openstack_helm_glance/vars/main.yml
@@ -0,0 +1,51 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_glance_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  storage: rbd
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_glance_image_repository }}/heat:{{ openstack_helm_glance_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_glance_image_repository }}/heat:{{ openstack_helm_glance_heat_image_tag }}"
+      db_init: "{{ openstack_helm_glance_image_repository }}/heat:{{ openstack_helm_glance_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_glance_image_repository }}/kubernetes-entrypoint:latest"
+      glance_api: "{{ openstack_helm_glance_image_repository }}/glance:{{ openstack_helm_glance_image_tag }}"
+      glance_db_sync: "{{ openstack_helm_glance_image_repository }}/glance:{{ openstack_helm_glance_image_tag }}"
+      glance_metadefs_load: "{{ openstack_helm_glance_image_repository }}/glance:{{ openstack_helm_glance_image_tag }}"
+      glance_registry: "{{ openstack_helm_glance_image_repository }}/glance:{{ openstack_helm_glance_image_tag }}"
+      glance_storage_init: "{{ openstack_helm_glance_image_repository }}/glance:{{ openstack_helm_glance_image_tag }}"
+      ks_endpoints: "{{ openstack_helm_glance_image_repository }}/heat:{{ openstack_helm_glance_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_glance_image_repository }}/heat:{{ openstack_helm_glance_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_glance_image_repository }}/heat:{{ openstack_helm_glance_heat_image_tag }}"
+      rabbit_init: "{{ openstack_helm_glance_image_repository }}/rabbitmq:3.8.23-management"
+  bootstrap:
+    enabled: false
+  pod:
+    replicas:
+      api: 3
+  conf:
+    glance:
+      DEFAULT:
+        log_config_append: null
+        show_image_direct_url: true
+        show_multiple_locations: true
+        enable_import_methods: "[]"
+      cors:
+        allowed_origins: "*"
+      image_formats:
+        disk_formats: "qcow2,raw"
+  manifests:
+    ingress_api: false
+    service_ingress_api: false
diff --git a/roles/openstack_helm_heat/defaults/main.yml b/roles/openstack_helm_heat/defaults/main.yml
new file mode 100644
index 0000000..3ca6123
--- /dev/null
+++ b/roles/openstack_helm_heat/defaults/main.yml
@@ -0,0 +1,27 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_heat_chart_repo_name: openstack-helm
+openstack_helm_heat_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_heat_chart_name: heat
+
+openstack_helm_heat_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_heat_image_tag: 16.0.1.dev16
+
+openstack_helm_heat_auth_encryption_key: "{{ undef(hint='You must specifiy an encryption key for Heat.') }}"
+
+openstack_helm_heat_diff: false
+openstack_helm_heat_migrate_from_mariadb: false
+
+openstack_helm_heat_values: {}
diff --git a/roles/openstack_helm_heat/meta/main.yml b/roles/openstack_helm_heat/meta/main.yml
new file mode 100644
index 0000000..3dfc746
--- /dev/null
+++ b/roles/openstack_helm_heat/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_heat_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_heat_chart_repo_url }}"
diff --git a/roles/openstack_helm_heat/tasks/main.yml b/roles/openstack_helm_heat/tasks/main.yml
new file mode 100644
index 0000000..7390441
--- /dev/null
+++ b/roles/openstack_helm_heat/tasks/main.yml
@@ -0,0 +1,74 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_heat_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_heat_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_heat_chart_name }}"
+
+- name: Generate Helm values comparison
+  ansible.builtin.include_role:
+    name: helm_diff
+  vars:
+    helm_diff_release_name: "{{ openstack_helm_heat_chart_name }}"
+    helm_diff_release_namespace: openstack
+    helm_diff_values: "{{ _openstack_helm_heat_values }}"
+  when:
+    - openstack_helm_heat_diff | bool
+
+- name: Migrate database from MariaDB to Percona XtraDB Cluster
+  ansible.builtin.include_role:
+    name: openstack_helm_migrate_to_percona_xtradb_cluster
+  vars:
+    openstack_helm_migrate_to_percona_xtradb_cluster_release_name: "{{ openstack_helm_heat_chart_name }}"
+    openstack_helm_migrate_to_percona_xtradb_cluster_release_namespace: openstack
+    openstack_helm_migrate_to_percona_xtradb_cluster_databases:
+      - heat
+    openstack_helm_migrate_to_percona_xtradb_cluster_services:
+      - kind: Deployment
+        name: heat-api
+      - kind: Deployment
+        name: heat-cfn
+      - kind: Deployment
+        name: heat-engine
+  when:
+    - openstack_helm_heat_migrate_from_mariadb | bool
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_heat_chart_name }}"
+    chart_ref: "{{ openstack_helm_heat_chart_repo_name }}/{{ openstack_helm_heat_chart_name }}"
+    chart_version: 0.2.8
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_heat_values }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: orchestration
+    openstack_helm_ingress_service_name: heat-api
+    openstack_helm_ingress_service_port: 8004
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: cloudformation
+    openstack_helm_ingress_service_name: heat-cfn
+    openstack_helm_ingress_service_port: 8000
diff --git a/roles/openstack_helm_heat/vars/main.yml b/roles/openstack_helm_heat/vars/main.yml
new file mode 100644
index 0000000..abec726
--- /dev/null
+++ b/roles/openstack_helm_heat/vars/main.yml
@@ -0,0 +1,54 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_heat_values: "{{ __openstack_helm_heat_values | combine(openstack_helm_heat_values, recursive=True) }}"
+__openstack_helm_heat_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      db_init: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_heat_image_repository }}/kubernetes-entrypoint:latest"
+      heat_api: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      heat_cfn: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      heat_cloudwatch: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      heat_db_sync: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      heat_engine_cleaner: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      heat_engine: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      heat_purge_deleted: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      ks_endpoints: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_heat_image_repository }}/heat:{{ openstack_helm_heat_image_tag }}"
+      rabbit_init: "{{ openstack_helm_heat_image_repository }}/rabbitmq:3.8.23-management"
+  pod:
+    replicas:
+      api: 3
+      cfn: 3
+      cloudwatch: 3
+      engine: 3
+  conf:
+    heat:
+      DEFAULT:
+        auth_encryption_key: "{{ openstack_helm_heat_auth_encryption_key }}"
+        log_config_append: null
+        region_name_for_services: "{{ openstack_helm_endpoints['identity']['auth']['heat']['region_name'] }}"
+        server_keystone_endpoint_type: public
+      clients_keystone:
+        endpoint_type: publicURL
+  manifests:
+    ingress_api: false
+    ingress_cfn: false
+    service_ingress_api: false
+    service_ingress_cfn: false
diff --git a/roles/openstack_helm_horizon/defaults/main.yml b/roles/openstack_helm_horizon/defaults/main.yml
new file mode 100644
index 0000000..ba6c691
--- /dev/null
+++ b/roles/openstack_helm_horizon/defaults/main.yml
@@ -0,0 +1,23 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_horizon_chart_repo_name: openstack-helm
+openstack_helm_horizon_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_horizon_chart_name: horizon
+
+openstack_helm_horizon_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_horizon_image_tag: 19.2.1.dev13
+openstack_helm_horizon_heat_image_tag: wallaby
+
+openstack_helm_horizon_values: {}
diff --git a/roles/openstack_helm_horizon/files/50-monasca-ui-settings.py b/roles/openstack_helm_horizon/files/50-monasca-ui-settings.py
new file mode 100644
index 0000000..28b4a99
--- /dev/null
+++ b/roles/openstack_helm_horizon/files/50-monasca-ui-settings.py
@@ -0,0 +1,56 @@
+from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+
+# Service group names (global across all projects):
+MONITORING_SERVICES_GROUPS = [
+    {'name': _('OpenStack Services'), 'groupBy': 'service'},
+    {'name': _('Servers'), 'groupBy': 'hostname'}
+]
+
+# Services being monitored
+MONITORING_SERVICES = getattr(
+    settings,
+    'MONITORING_SERVICES_GROUPS',
+    MONITORING_SERVICES_GROUPS
+)
+
+MONITORING_SERVICE_VERSION = getattr(
+    settings, 'MONITORING_SERVICE_VERSION', '2_0'
+)
+MONITORING_SERVICE_TYPE = getattr(
+    settings, 'MONITORING_SERVICE_TYPE', 'monitoring'
+)
+MONITORING_ENDPOINT_TYPE = getattr(
+    # NOTE(trebskit) # will default to OPENSTACK_ENDPOINT_TYPE
+    settings, 'MONITORING_ENDPOINT_TYPE', None
+)
+
+# Grafana button titles/file names (global across all projects):
+# GRAFANA_LINKS = [{"raw": True, "path": "monasca-dashboard", "title": "Sub page1"}]
+GRAFANA_LINKS = []
+DASHBOARDS = getattr(settings, 'GRAFANA_LINKS', GRAFANA_LINKS)
+
+GRAFANA_URL = {"regionOne": "/grafana"}
+
+SHOW_GRAFANA_HOME = getattr(settings, 'SHOW_GRAFANA_HOME', True)
+
+ENABLE_LOG_MANAGEMENT_BUTTON = getattr(
+    settings, 'ENABLE_LOG_MANAGEMENT_BUTTON', False)
+ENABLE_EVENT_MANAGEMENT_BUTTON = getattr(
+    settings, 'ENABLE_EVENT_MANAGEMENT_BUTTON', False)
+
+KIBANA_POLICY_RULE = getattr(settings, 'KIBANA_POLICY_RULE',
+                             'monitoring:kibana_access')
+KIBANA_POLICY_SCOPE = getattr(settings, 'KIBANA_POLICY_SCOPE',
+                              'monitoring')
+KIBANA_HOST = getattr(settings, 'KIBANA_HOST',
+                      'http://192.168.10.6:5601/')
+
+OPENSTACK_SSL_NO_VERIFY = getattr(
+    settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+OPENSTACK_SSL_CACERT = getattr(
+    settings, 'OPENSTACK_SSL_CACERT', None)
+
+POLICY_FILES = getattr(settings, 'POLICY_FILES', {})
+POLICY_FILES.update({'monitoring': 'monitoring_policy.json', })  # noqa
+setattr(settings, 'POLICY_FILES', POLICY_FILES)
diff --git a/roles/openstack_helm_horizon/meta/main.yml b/roles/openstack_helm_horizon/meta/main.yml
new file mode 100644
index 0000000..8027f7c
--- /dev/null
+++ b/roles/openstack_helm_horizon/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_horizon_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_horizon_chart_repo_url }}"
diff --git a/roles/openstack_helm_horizon/tasks/main.yml b/roles/openstack_helm_horizon/tasks/main.yml
new file mode 100644
index 0000000..58bea45
--- /dev/null
+++ b/roles/openstack_helm_horizon/tasks/main.yml
@@ -0,0 +1,46 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_horizon_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_horizon_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_horizon_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_horizon_chart_name }}"
+    chart_ref: "{{ openstack_helm_horizon_chart_repo_name }}/{{ openstack_helm_horizon_chart_name }}"
+    chart_version: 0.2.16
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_horizon_values | combine(openstack_helm_horizon_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: dashboard
+    openstack_helm_ingress_service_name: horizon-int
+    openstack_helm_ingress_service_port: 80
+    openstack_helm_ingress_paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: grafana
+            port:
+              number: 80
diff --git a/roles/openstack_helm_horizon/vars/main.yml b/roles/openstack_helm_horizon/vars/main.yml
new file mode 100644
index 0000000..b63c869
--- /dev/null
+++ b/roles/openstack_helm_horizon/vars/main.yml
@@ -0,0 +1,60 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_horizon_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_horizon_image_repository }}/heat:{{ openstack_helm_horizon_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_horizon_image_repository }}/heat:{{ openstack_helm_horizon_heat_image_tag }}"
+      db_init: "{{ openstack_helm_horizon_image_repository }}/heat:{{ openstack_helm_horizon_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_horizon_image_repository }}/kubernetes-entrypoint:latest"
+      ks_endpoints: "{{ openstack_helm_horizon_image_repository }}/heat:{{ openstack_helm_horizon_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_horizon_image_repository }}/heat:{{ openstack_helm_horizon_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_horizon_image_repository }}/heat:{{ openstack_helm_horizon_heat_image_tag }}"
+      horizon_db_sync: "{{ openstack_helm_horizon_image_repository }}/horizon:{{ openstack_helm_horizon_image_tag }}"
+      horizon: "{{ openstack_helm_horizon_image_repository }}/horizon:{{ openstack_helm_horizon_image_tag }}"
+      rabbit_init: "{{ openstack_helm_horizon_image_repository }}/rabbitmq:3.8.23-management"
+  pod:
+    replicas:
+      server: 3
+  conf:
+    horizon:
+      local_settings:
+        config:
+          secure_proxy_ssl_header: "True"
+          horizon_images_upload_mode: direct
+          openstack_enable_password_retrieve: "True"
+          raw:
+            WEBSSO_KEYSTONE_URL: "https://{{ openstack_helm_endpoints['identity']['scheme']['public'] }}://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}/v3"
+      local_settings_d:
+        _50_monasca_ui_settings: "{{ lookup('file', '50-monasca-ui-settings.py') }}"
+      extra_panels:
+        - designatedashboard
+        - heat_dashboard
+        - ironic_ui
+        - magnum_ui
+        - monitoring
+        - neutron_vpnaas_dashboard
+        - octavia_dashboard
+        - senlin_dashboard
+      policy:
+        monitoring:
+          default: "@"
+          monasca_user_role: role:monasca-user
+          monitoring:monitoring: rule:monasca_user_role
+          monitoring:kibana_access: rule:monasca_user_role
+  manifests:
+    ingress_api: false
+    service_ingress_api: false
diff --git a/roles/openstack_helm_infra_ceph_provisioners/defaults/main.yml b/roles/openstack_helm_infra_ceph_provisioners/defaults/main.yml
new file mode 100644
index 0000000..f450a53
--- /dev/null
+++ b/roles/openstack_helm_infra_ceph_provisioners/defaults/main.yml
@@ -0,0 +1,23 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_infra_ceph_provisioners_chart_repo_name: openstack-helm-infra
+openstack_helm_infra_ceph_provisioners_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm-infra/
+openstack_helm_infra_ceph_provisioners_chart_name: ceph-provisioners
+
+openstack_helm_infra_ceph_provisioners_ceph_monitors: "{{ _ceph_csi_rbd_helm_info.status['values']['csiConfig'][0]['monitors'] }}"
+openstack_helm_infra_ceph_provisioners_ceph_fsid: "{{ _ceph_csi_rbd_helm_info.status['values']['csiConfig'][0]['clusterID'] }}"
+
+openstack_helm_infra_ceph_provisioners_ceph_public_network: "{{ ceph_mon_public_network }}"
+openstack_helm_infra_ceph_provisioners_ceph_cluster_network: "{{ openstack_helm_infra_ceph_provisioners_ceph_public_network }}"
diff --git a/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml b/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml
new file mode 100644
index 0000000..7f7df71
--- /dev/null
+++ b/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_infra_ceph_provisioners_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_infra_ceph_provisioners_chart_repo_url }}"
+  - ceph_csi_rbd
diff --git a/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml b/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml
new file mode 100644
index 0000000..b43c3b1
--- /dev/null
+++ b/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml
@@ -0,0 +1,128 @@
+# Copyright (c) 2022 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.
+
+- name: Retrieve Helm values for "ceph-csi-rbd"
+  kubernetes.core.helm_info:
+    name: ceph-csi-rbd
+    release_namespace: kube-system
+  register: _ceph_csi_rbd_helm_info
+
+- name: Create Ceph service
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Service
+      metadata:
+        name: ceph-mon
+        namespace: openstack
+        labels:
+          application: ceph
+      spec:
+        clusterIP: None
+        ports:
+          - name: mon
+            port: 6789
+            targetPort: 6789
+          - name: mon-msgr2
+            port: 3300
+            targetPort: 3300
+          - name: metrics
+            port: 9283
+            targetPort: 9283
+
+- name: Generate Ceph endpoint list
+  ansible.builtin.set_fact:
+    _openstack_helm_infra_ceph_provisioners_ceph_monitors: |
+      {{ 
+        _openstack_helm_infra_ceph_provisioners_ceph_monitors | default([]) +
+          [{'ip': item}]
+      }}
+  loop: "{{ openstack_helm_infra_ceph_provisioners_ceph_monitors }}"
+
+- name: Create Ceph endpoints
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Endpoints
+      metadata:
+        name: ceph-mon
+        namespace: openstack
+        labels:
+          application: ceph
+      subsets:
+        - addresses: "{{ _openstack_helm_infra_ceph_provisioners_ceph_monitors }}"
+          ports:
+            - name: mon
+              port: 6789
+              protocol: TCP
+            - name: mon-msgr2
+              port: 3300
+              protocol: TCP
+            - name: metrics
+              port: 9283
+              protocol: TCP
+
+- name: Retrieve client.admin keyring
+  vexxhost.atmosphere.ceph_key:
+    name: client.admin
+    state: info
+    output_format: json
+  register: _openstack_helm_infra_ceph_provisioners_ceph_key
+
+- name: Parse client.admin keyring
+  ansible.builtin.set_fact:
+    _openstack_helm_infra_ceph_provisioners_keyring: "{{ _openstack_helm_infra_ceph_provisioners_ceph_key.stdout | from_json | first }}"
+
+- name: Create "pvc-ceph-client-key" secret
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Secret
+      type: kubernetes.io/rbd
+      metadata:
+        name: pvc-ceph-client-key
+        namespace: openstack
+        labels:
+          application: ceph
+      stringData:
+        key: "{{ _openstack_helm_infra_ceph_provisioners_keyring.key }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_infra_ceph_provisioners_chart_name }}"
+    chart_ref: "{{ openstack_helm_infra_ceph_provisioners_chart_repo_name }}/{{ openstack_helm_infra_ceph_provisioners_chart_name }}"
+    chart_version: 0.1.17
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      network:
+        public: "{{ openstack_helm_infra_ceph_provisioners_ceph_public_network }}"
+        cluster: "{{ openstack_helm_infra_ceph_provisioners_ceph_cluster_network }}"
+      conf:
+        ceph:
+          global:
+            fsid: "{{ openstack_helm_infra_ceph_provisioners_ceph_fsid }}"
+      manifests:
+        configmap_bin: false
+        configmap_bin_common: false
+        deployment_rbd_provisioner: false
+        deployment_csi_rbd_provisioner: false
+        deployment_cephfs_provisioner: false
+        job_cephfs_client_key: false
+        job_namespace_client_key_cleaner: false
+        job_namespace_client_key: false
+        storageclass: false
diff --git a/roles/openstack_helm_infra_libvirt/defaults/main.yml b/roles/openstack_helm_infra_libvirt/defaults/main.yml
new file mode 100644
index 0000000..c2c4511
--- /dev/null
+++ b/roles/openstack_helm_infra_libvirt/defaults/main.yml
@@ -0,0 +1,22 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_infra_libvirt_chart_repo_name: openstack-helm-infra
+openstack_helm_infra_libvirt_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm-infra/
+openstack_helm_infra_libvirt_chart_name: libvirt
+
+openstack_helm_infra_libvirt_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_infra_libvirt_image_tag: wallaby
+
+openstack_helm_infra_libvirt_values: {}
diff --git a/roles/openstack_helm_infra_libvirt/meta/main.yml b/roles/openstack_helm_infra_libvirt/meta/main.yml
new file mode 100644
index 0000000..67fb174
--- /dev/null
+++ b/roles/openstack_helm_infra_libvirt/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_infra_libvirt_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_infra_libvirt_chart_repo_url }}"
diff --git a/roles/openstack_helm_infra_libvirt/tasks/main.yml b/roles/openstack_helm_infra_libvirt/tasks/main.yml
new file mode 100644
index 0000000..46df5d1
--- /dev/null
+++ b/roles/openstack_helm_infra_libvirt/tasks/main.yml
@@ -0,0 +1,30 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_infra_libvirt_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_infra_libvirt_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_infra_libvirt_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_infra_libvirt_chart_name }}"
+    chart_ref: "{{ openstack_helm_infra_libvirt_chart_repo_name }}/{{ openstack_helm_infra_libvirt_chart_name }}"
+    chart_version: 0.1.8
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_infra_libvirt_values | combine(openstack_helm_infra_libvirt_values, recursive=True) }}"
diff --git a/roles/openstack_helm_infra_libvirt/vars/main.yml b/roles/openstack_helm_infra_libvirt/vars/main.yml
new file mode 100644
index 0000000..f79b2a8
--- /dev/null
+++ b/roles/openstack_helm_infra_libvirt/vars/main.yml
@@ -0,0 +1,24 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_infra_libvirt_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      ceph_config_helper: "{{ openstack_helm_infra_libvirt_image_repository }}/libvirt:{{ openstack_helm_infra_libvirt_image_tag }}"
+      dep_check: "{{ openstack_helm_infra_libvirt_image_repository }}/kubernetes-entrypoint:latest"
+      libvirt: "{{ openstack_helm_infra_libvirt_image_repository }}/libvirt:{{ openstack_helm_infra_libvirt_image_tag }}"
+  conf:
+    libvirt:
+      listen_addr: 0.0.0.0
diff --git a/roles/openstack_helm_infra_memcached/defaults/main.yml b/roles/openstack_helm_infra_memcached/defaults/main.yml
new file mode 100644
index 0000000..23a09da
--- /dev/null
+++ b/roles/openstack_helm_infra_memcached/defaults/main.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_infra_memcached_chart_repo_name: openstack-helm-infra
+openstack_helm_infra_memcached_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm-infra/
+openstack_helm_infra_memcached_chart_name: memcached
+
+openstack_helm_infra_memcached_diff: false
+openstack_helm_infra_memcached_values: {}
diff --git a/roles/openstack_helm_infra_memcached/meta/main.yml b/roles/openstack_helm_infra_memcached/meta/main.yml
new file mode 100644
index 0000000..0157d71
--- /dev/null
+++ b/roles/openstack_helm_infra_memcached/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_infra_memcached_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_infra_memcached_chart_repo_url }}"
diff --git a/roles/openstack_helm_infra_memcached/tasks/main.yml b/roles/openstack_helm_infra_memcached/tasks/main.yml
new file mode 100644
index 0000000..491e3bc
--- /dev/null
+++ b/roles/openstack_helm_infra_memcached/tasks/main.yml
@@ -0,0 +1,122 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_infra_memcached_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_infra_memcached_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_infra_memcached_chart_name }}"
+
+- name: Generate Helm values comparison
+  ansible.builtin.include_role:
+    name: helm_diff
+  vars:
+    helm_diff_release_name: "{{ openstack_helm_infra_memcached_chart_name }}"
+    helm_diff_release_namespace: openstack
+    helm_diff_values: "{{ _openstack_helm_infra_memcached_values }}"
+  when:
+    - openstack_helm_infra_memcached_diff | bool
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_infra_memcached_chart_name }}"
+    chart_ref: "{{ openstack_helm_infra_memcached_chart_repo_name }}/{{ openstack_helm_infra_memcached_chart_name }}"
+    chart_version: 0.1.6
+    release_namespace: openstack
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_infra_memcached_values }}"
+
+- name: Create Service for metrics
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Service
+      metadata:
+        name: memcached-metrics
+        namespace: openstack
+        labels:
+          application: memcached
+          component: server
+      spec:
+        selector:
+          application: memcached
+          component: server
+        ports:
+          - name: metrics
+            port: 9150
+            targetPort: 9150
+
+- name: Create ServiceMonitor
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: monitoring.coreos.com/v1
+      kind: ServiceMonitor
+      metadata:
+        name: memcached
+        namespace: monitoring
+        labels:
+          release: kube-prometheus-stack
+      spec:
+        jobLabel: application
+        endpoints:
+          - port: "metrics"
+            path: "/metrics"
+            relabelings:
+              - sourceLabels: ["__meta_kubernetes_pod_name"]
+                targetLabel: "instance"
+              - action: "labeldrop"
+                regex: "^(container|endpoint|namespace|pod|service)$"
+        namespaceSelector:
+          matchNames:
+            - openstack
+        selector:
+          matchLabels:
+            application: memcached
+            component: server
+
+- name: Create PrometheusRule
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: monitoring.coreos.com/v1
+      kind: PrometheusRule
+      metadata:
+        name: memcached
+        namespace: monitoring
+        labels:
+          release: kube-prometheus-stack
+      spec:
+        groups:
+          - name: memcached
+            rules:
+              - alert: MemcachedDown
+                expr: memcached_up == 0
+                for: 5m
+                labels:
+                  severity: critical
+              - alert: MemcachedConnectionLimitApproaching
+                expr: (memcached_current_connections / memcached_max_connections * 100) > 80
+                for: 5m
+                labels:
+                  severity: warning
+              - alert: MemcachedConnectionLimitApproaching
+                expr: (memcached_current_connections / memcached_max_connections * 100) > 95
+                for: 5m
+                labels:
+                  severity: critical
diff --git a/roles/openstack_helm_infra_memcached/vars/main.yml b/roles/openstack_helm_infra_memcached/vars/main.yml
new file mode 100644
index 0000000..4acb969
--- /dev/null
+++ b/roles/openstack_helm_infra_memcached/vars/main.yml
@@ -0,0 +1,24 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_infra_memcached_values: "{{ __openstack_helm_infra_memcached_values | combine(openstack_helm_infra_memcached_values, recursive=True) }}"
+__openstack_helm_infra_memcached_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      memcached: quay.io/vexxhost/memcached:1.6.9
+      prometheus_memcached_exporter: quay.io/vexxhost/memcached-exporter:v0.9.0-1
+  monitoring:
+    prometheus:
+      enabled: true
diff --git a/roles/openstack_helm_infra_openvswitch/defaults/main.yml b/roles/openstack_helm_infra_openvswitch/defaults/main.yml
new file mode 100644
index 0000000..c8c6a93
--- /dev/null
+++ b/roles/openstack_helm_infra_openvswitch/defaults/main.yml
@@ -0,0 +1,22 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_infra_openvswitch_chart_repo_name: openstack-helm-infra
+openstack_helm_infra_openvswitch_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm-infra/
+openstack_helm_infra_openvswitch_chart_name: openvswitch
+
+openstack_helm_infra_openvswitch_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_infra_openvswitch_image_tag: wallaby
+
+openstack_helm_infra_openvswitch_values: {}
diff --git a/roles/openstack_helm_infra_openvswitch/meta/main.yml b/roles/openstack_helm_infra_openvswitch/meta/main.yml
new file mode 100644
index 0000000..f31873c
--- /dev/null
+++ b/roles/openstack_helm_infra_openvswitch/meta/main.yml
@@ -0,0 +1,20 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_infra_openvswitch_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_infra_openvswitch_chart_repo_url }}"
+  - openstack_namespace
diff --git a/roles/openstack_helm_infra_openvswitch/tasks/main.yml b/roles/openstack_helm_infra_openvswitch/tasks/main.yml
new file mode 100644
index 0000000..372a5e4
--- /dev/null
+++ b/roles/openstack_helm_infra_openvswitch/tasks/main.yml
@@ -0,0 +1,30 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_infra_openvswitch_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_infra_openvswitch_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_infra_openvswitch_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_infra_openvswitch_chart_name }}"
+    chart_ref: "{{ openstack_helm_infra_openvswitch_chart_repo_name }}/{{ openstack_helm_infra_openvswitch_chart_name }}"
+    chart_version: 0.1.6
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_infra_openvswitch_values | combine(openstack_helm_infra_openvswitch_values, recursive=True) }}"
diff --git a/roles/openstack_helm_infra_openvswitch/vars/main.yml b/roles/openstack_helm_infra_openvswitch/vars/main.yml
new file mode 100644
index 0000000..e84639e
--- /dev/null
+++ b/roles/openstack_helm_infra_openvswitch/vars/main.yml
@@ -0,0 +1,21 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_infra_openvswitch_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      dep_check: "{{ openstack_helm_infra_openvswitch_image_repository }}/kubernetes-entrypoint:latest"
+      openvswitch_db_server: "{{ openstack_helm_infra_openvswitch_image_repository }}/openvswitch:{{ openstack_helm_infra_openvswitch_image_tag }}"
+      openvswitch_vswitchd: "{{ openstack_helm_infra_openvswitch_image_repository }}/openvswitch:{{ openstack_helm_infra_openvswitch_image_tag }}"
diff --git a/roles/openstack_helm_infra_rabbitmq/defaults/main.yml b/roles/openstack_helm_infra_rabbitmq/defaults/main.yml
new file mode 100644
index 0000000..b248ba7
--- /dev/null
+++ b/roles/openstack_helm_infra_rabbitmq/defaults/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_infra_rabbitmq_chart_repo_name: openstack-helm-infra
+openstack_helm_infra_rabbitmq_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm-infra/
+openstack_helm_infra_rabbitmq_chart_name: rabbitmq
+
+openstack_helm_infra_rabbitmq_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
diff --git a/roles/openstack_helm_infra_rabbitmq/meta/main.yml b/roles/openstack_helm_infra_rabbitmq/meta/main.yml
new file mode 100644
index 0000000..53614f8
--- /dev/null
+++ b/roles/openstack_helm_infra_rabbitmq/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_infra_rabbitmq_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_infra_rabbitmq_chart_repo_url }}"
diff --git a/roles/openstack_helm_infra_rabbitmq/tasks/main.yml b/roles/openstack_helm_infra_rabbitmq/tasks/main.yml
new file mode 100644
index 0000000..7e50b0e
--- /dev/null
+++ b/roles/openstack_helm_infra_rabbitmq/tasks/main.yml
@@ -0,0 +1,159 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_infra_rabbitmq_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_infra_rabbitmq_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_infra_rabbitmq_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_infra_rabbitmq_chart_name }}"
+    chart_ref: "{{ openstack_helm_infra_rabbitmq_chart_repo_name }}/{{ openstack_helm_infra_rabbitmq_chart_name }}"
+    chart_version: 0.1.15
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      endpoints: "{{ openstack_helm_endpoints }}"
+      images:
+        tags:
+          dep_check: "{{ openstack_helm_infra_rabbitmq_image_repository }}/kubernetes-entrypoint:latest"
+          rabbitmq_init: "{{ openstack_helm_infra_rabbitmq_image_repository }}/cli:latest"
+          rabbitmq: "{{ openstack_helm_infra_rabbitmq_image_repository }}/rabbitmq:3.8.23"
+      pod:
+        replicas:
+          server: 3
+      conf:
+        enabled_plugins:
+          - rabbitmq_management
+          - rabbitmq_peer_discovery_k8s
+          - rabbitmq_prometheus
+      volume:
+        size: 10Gi
+      manifests:
+        ingress_management: false
+
+- name: Create Service for metrics
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Service
+      metadata:
+        name: rabbitmq-metrics
+        namespace: openstack
+        labels:
+          application: rabbitmq
+          component: server
+      spec:
+        selector:
+          application: rabbitmq
+          component: server
+        ports:
+          - name: metrics
+            port: 15692
+            targetPort: 15692
+
+- name: Create ServiceMonitor
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: monitoring.coreos.com/v1
+      kind: ServiceMonitor
+      metadata:
+        name: rabbitmq
+        namespace: monitoring
+        labels:
+          release: kube-prometheus-stack
+      spec:
+        jobLabel: application
+        endpoints:
+          - port: "metrics"
+            path: "/metrics"
+            relabelings:
+              - sourceLabels: ["__meta_kubernetes_pod_name"]
+                targetLabel: "instance"
+              - action: "labeldrop"
+                regex: "^(container|endpoint|namespace|pod|service)$"
+        namespaceSelector:
+          matchNames:
+            - openstack
+        selector:
+          matchLabels:
+            application: rabbitmq
+            component: server
+
+- name: Create PrometheusRule
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: monitoring.coreos.com/v1
+      kind: PrometheusRule
+      metadata:
+        name: rabbitmq
+        namespace: monitoring
+        labels:
+          release: kube-prometheus-stack
+      spec:
+        groups:
+          - name: rules
+            rules:
+              - alert: RabbitmqDown
+                expr: absent(rabbitmq_build_info)
+                for: 1m
+                labels:
+                  severity: critical
+              - alert: RabbitmqNodeDown
+                expr: sum(rabbitmq_build_info) < 3
+                for: 1m
+                labels:
+                  severity: critical
+              - alert: RabbitmqNodeNotDistributed
+                expr: erlang_vm_dist_node_state < 3
+                for: 1m
+                labels:
+                  severity: critical
+              - alert: RabbitmqMemoryHigh
+                expr: rabbitmq_process_resident_memory_bytes / rabbitmq_resident_memory_limit_bytes > 0.80
+                labels:
+                  severity: warning
+              - alert: RabbitmqMemoryHigh
+                expr: rabbitmq_process_resident_memory_bytes / rabbitmq_resident_memory_limit_bytes > 0.95
+                labels:
+                  severity: critical
+              - alert: RabbitmqFileDescriptorsUsage
+                expr: rabbitmq_process_open_fds / rabbitmq_process_max_fds > 0.80
+                labels:
+                  severity: warning
+              - alert: RabbitmqFileDescriptorsUsage
+                expr: rabbitmq_process_open_fds / rabbitmq_process_max_fds > 0.95
+                labels:
+                  severity: critical
+              - alert: RabbitmqUnackedMessages
+                expr: sum(rabbitmq_queue_messages_unacked) BY (queue) > 1000
+                for: 5m
+                labels:
+                  severity: warning
+              - alert: RabbitmqUnackedMessages
+                expr: sum(rabbitmq_queue_messages_unacked) BY (queue) > 1000
+                for: 1h
+                labels:
+                  severity: critical
+              - alert: RabbitmqConnections
+                expr: rabbitmq_connections > 1000
+                labels:
+                  severity: warning
diff --git a/roles/openstack_helm_ingress/defaults/main.yml b/roles/openstack_helm_ingress/defaults/main.yml
new file mode 100644
index 0000000..9ec5bd9
--- /dev/null
+++ b/roles/openstack_helm_ingress/defaults/main.yml
@@ -0,0 +1,16 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_ingress_annotations: {}
+openstack_helm_ingress_paths: []
diff --git a/roles/openstack_helm_ingress/tasks/main.yml b/roles/openstack_helm_ingress/tasks/main.yml
new file mode 100644
index 0000000..11b0cdf
--- /dev/null
+++ b/roles/openstack_helm_ingress/tasks/main.yml
@@ -0,0 +1,34 @@
+# Copyright (c) 2022 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.
+
+- name: Create Ingress ({{ openstack_helm_ingress_endpoint }})
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Ingress
+      metadata:
+        name: "{{ openstack_helm_ingress_endpoint | replace('_', '-') }}"
+        namespace: openstack
+        annotations: "{{ _openstack_helm_ingress_annotations | combine(openstack_helm_ingress_annotations, recursive=True) }}"
+      spec:
+        ingressClassName: openstack
+        rules:
+          - host: "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['host'] }}"
+            http:
+              paths: "{{ _openstack_helm_ingress_paths }}"
+        tls:
+          - secretName: "{{ openstack_helm_ingress_service_name }}-certs"
+            hosts:
+              - "{{ openstack_helm_endpoints[openstack_helm_ingress_endpoint]['host_fqdn_override']['public']['host'] }}"
diff --git a/roles/openstack_helm_ingress/vars/main.yml b/roles/openstack_helm_ingress/vars/main.yml
new file mode 100644
index 0000000..a000c50
--- /dev/null
+++ b/roles/openstack_helm_ingress/vars/main.yml
@@ -0,0 +1,26 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_ingress_annotations:
+  cert-manager.io/issuer: openstack
+
+_openstack_helm_ingress_paths: "{{ openstack_helm_ingress_paths + __openstack_helm_ingress_paths }}"
+__openstack_helm_ingress_paths:
+  - path: /
+    pathType: Prefix
+    backend:
+      service:
+        name: "{{ openstack_helm_ingress_service_name }}"
+        port:
+          number: "{{ openstack_helm_ingress_service_port }}"
diff --git a/roles/openstack_helm_keystone/defaults/main.yml b/roles/openstack_helm_keystone/defaults/main.yml
new file mode 100644
index 0000000..7f2358b
--- /dev/null
+++ b/roles/openstack_helm_keystone/defaults/main.yml
@@ -0,0 +1,23 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_keystone_chart_repo_name: openstack-helm
+openstack_helm_keystone_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_keystone_chart_name: keystone
+
+openstack_helm_keystone_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_keystone_image_tag: 19.0.1.dev11
+openstack_helm_keystone_heat_image_tag: wallaby
+
+openstack_helm_keystone_values: {}
diff --git a/roles/openstack_helm_keystone/meta/main.yml b/roles/openstack_helm_keystone/meta/main.yml
new file mode 100644
index 0000000..136ce7c
--- /dev/null
+++ b/roles/openstack_helm_keystone/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_keystone_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_keystone_chart_repo_url }}"
diff --git a/roles/openstack_helm_keystone/tasks/main.yml b/roles/openstack_helm_keystone/tasks/main.yml
new file mode 100644
index 0000000..45e2de5
--- /dev/null
+++ b/roles/openstack_helm_keystone/tasks/main.yml
@@ -0,0 +1,39 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_keystone_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_keystone_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_keystone_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_keystone_chart_name }}"
+    chart_ref: "{{ openstack_helm_keystone_chart_repo_name }}/{{ openstack_helm_keystone_chart_name }}"
+    chart_version: 0.2.19
+    release_namespace: openstack
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_keystone_values | combine(openstack_helm_keystone_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: identity
+    openstack_helm_ingress_service_name: keystone-api
+    openstack_helm_ingress_service_port: 5000
diff --git a/roles/openstack_helm_keystone/vars/main.yml b/roles/openstack_helm_keystone/vars/main.yml
new file mode 100644
index 0000000..5b384c1
--- /dev/null
+++ b/roles/openstack_helm_keystone/vars/main.yml
@@ -0,0 +1,229 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_keystone_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_keystone_image_repository }}/heat:{{ openstack_helm_keystone_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_keystone_image_repository }}/heat:{{ openstack_helm_keystone_heat_image_tag }}"
+      db_init: "{{ openstack_helm_keystone_image_repository }}/heat:{{ openstack_helm_keystone_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_keystone_image_repository }}/kubernetes-entrypoint:latest"
+      keystone_api: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      keystone_credential_cleanup: "{{ openstack_helm_keystone_image_repository }}/heat:{{ openstack_helm_keystone_heat_image_tag }}"
+      keystone_credential_rotate: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      keystone_credential_setup: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      keystone_db_sync: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      keystone_domain_manage: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      keystone_fernet_rotate: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      keystone_fernet_setup: "{{ openstack_helm_keystone_image_repository }}/keystone:{{ openstack_helm_keystone_image_tag }}"
+      ks_user: "{{ openstack_helm_keystone_image_repository }}/heat:{{ openstack_helm_keystone_heat_image_tag }}"
+      rabbit_init: "{{ openstack_helm_keystone_image_repository }}/rabbitmq:3.8.23-management"
+  pod:
+    #     mounts = {
+    #       keystone_api = {
+    #         keystone_api = {
+    #           volumeMounts = [
+    #             {
+    #               name      = kubernetes_config_map.keystone_ldap_ca.metadata[0].name
+    #               mountPath = "/etc/keystone/ldap"
+    #             },
+    #             {
+    #               name      = kubernetes_config_map.keystone_openid_connect_metadata.metadata[0].name
+    #               mountPath = "/var/lib/apache2/oidc"
+    #             }
+    #           ],
+    #           volumes = [
+    #             {
+    #               name = kubernetes_config_map.keystone_ldap_ca.metadata[0].name
+    #               configMap = {
+    #                 name = kubernetes_config_map.keystone_ldap_ca.metadata[0].name
+    #               }
+    #             },
+    #             {
+    #               name = kubernetes_config_map.keystone_openid_connect_metadata.metadata[0].name
+    #               configMap = {
+    #                 name = kubernetes_config_map.keystone_openid_connect_metadata.metadata[0].name
+    #               }
+    #             }
+    #           ]
+    #         }
+    #       }
+    #     },
+    replicas:
+      api: 3
+  conf:
+    keystone:
+      DEFAULT:
+        log_config_append: null
+      auth:
+        methods: password,token,openid,application_credential
+      cors:
+        allowed_origins: "*"
+      federation:
+        assertion_prefix: OIDC-
+        remote_id_attribute: OIDC-iss
+        # TODO(mnaser): Lookup using openstack_helm_endpoints
+        trusted_dashboard: "https://{{ openstack_helm_endpoints_horizon_api_host }}/auth/websso/"
+      identity:
+        domain_configuration_from_database: true
+  manifests:
+    job_credential_cleanup: false
+    ingress_api: false
+    service_ingress_api: false
+# # LDAP configuration
+# yamlencode({
+#   conf = {
+#     ks_domains = {
+#       for domain, details in var.keystone_ldap_domains : domain => {
+#         identity = {
+#           driver = "ldap"
+#         }
+#         ldap = merge({
+#           tls_cacertfile = "/etc/keystone/ldap/${domain}.crt"
+#         }, details.conf)
+#       }
+#     }
+#   }
+# }),
+
+# # OpenID Connect
+# yamlencode({
+#   bootstrap = {
+#     script = <<-EOT
+#     # Create role for publishing images
+#     openstack role create --or-show image-publisher
+
+#     # Add member role for admin user
+#     openstack role add \
+#           --user="$${OS_USERNAME}" \
+#           --user-domain="$${OS_USER_DOMAIN_NAME}" \
+#           --project-domain="$${OS_PROJECT_DOMAIN_NAME}" \
+#           --project="$${OS_PROJECT_NAME}" \
+#           "member"
+
+#     # Create project for tempest-pushgateway
+#     openstack project create --or-show \
+#       "${kubernetes_secret.tempest_pushgateway.data.OS_PROJECT_NAME}"
+#     openstack user create --or-show \
+#       "${kubernetes_secret.tempest_pushgateway.data.OS_USERNAME}"
+#     openstack user set \
+#       --password="${kubernetes_secret.tempest_pushgateway.data.OS_PASSWORD}" \
+#       "${kubernetes_secret.tempest_pushgateway.data.OS_USERNAME}"
+#     openstack role add \
+#       --user="${kubernetes_secret.tempest_pushgateway.data.OS_USERNAME}" \
+#       --project="${kubernetes_secret.tempest_pushgateway.data.OS_PROJECT_NAME}" \
+#       "member"
+
+#     # Add admin user to default domain
+#     openstack role add \
+#           --user="$${OS_USERNAME}" \
+#           --domain="$${OS_DEFAULT_DOMAIN}" \
+#           "admin"
+#     %{for name, config in var.keystone_openid_connect_idps}
+#     # OpenID connect (${name})
+
+#     # Create Identity provider if it doesn't exist
+#     IDP_ID=$(openstack identity provider show ${name} -c id -f value || :)
+#     if [ -z "$IDP_ID" ]; then
+#         openstack identity provider create --remote-id ${config.issuer} ${name}
+#     else
+#         openstack identity provider set --remote-id ${config.issuer} ${name}
+#     fi
+
+#     # Generate mapping
+#     cat <<EOF | tee /tmp/mapping.json
+#     ${jsonencode(local.keystone_mappings[name])}
+#     EOF
+
+#     # Upload mapping to Keystone
+#     MAPPING_ID=$(openstack mapping show ${name} -c id -f value || :)
+#     if [ -z "$MAPPING_ID" ]; then
+#         openstack mapping create --rules /tmp/mapping.json ${name}
+#     else
+#         openstack mapping set --rules /tmp/mapping.json ${name}
+#     fi
+
+#     # Create federation
+#     FEDERATION_ID=$(openstack federation protocol show --identity-provider ${name} openid -c id -f value || :)
+#     if [ -z "$FEDERATION_ID" ]; then
+#         openstack federation protocol create --identity-provider ${name} --mapping ${name} openid
+#     fi
+#     %{endfor~}
+#     EOT
+#   }
+#   conf = {
+#     wsgi_keystone = <<-EOT
+#     {{- $portInt := tuple "identity" "internal" "api" $ | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+
+#     Listen 0.0.0.0:{{ $portInt }}
+
+#     LogFormat "%h %l %u %t \"%r\" %>s %b \"%%{Referer}i\" \"%%{User-Agent}i\"" combined
+#     LogFormat "%%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%%{Referer}i\" \"%%{User-Agent}i\"" proxy
+
+#     SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
+#     CustomLog /dev/stdout combined env=!forwarded
+#     CustomLog /dev/stdout proxy env=forwarded
+
+#     <VirtualHost *:{{ $portInt }}>
+#         WSGIDaemonProcess keystone-public processes=4 threads=1 user=keystone group=keystone display-name=%%{GROUP}
+#         WSGIProcessGroup keystone-public
+#         WSGIScriptAlias / /var/www/cgi-bin/keystone/keystone-wsgi-public
+#         WSGIApplicationGroup %%{GLOBAL}
+#         WSGIPassAuthorization On
+#         <IfVersion >= 2.4>
+#           ErrorLogFormat "%%{cu}t %M"
+#         </IfVersion>
+#         ErrorLog /dev/stdout
+
+#         SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
+#         CustomLog /dev/stdout combined env=!forwarded
+#         CustomLog /dev/stdout proxy env=forwarded
+
+#         # OpenID connect
+#         OIDCMetadataDir /var/lib/apache2/oidc
+#         OIDCClaimPrefix "OIDC-"
+#         OIDCSessionType client-cookie
+#         OIDCCryptoPassphrase ${random_password.keystone_openid_connect_crypto_passphrase.result}
+#         OIDCRedirectURLsAllowed ^https://${var.horizon_api_host}/auth/logout/$ ^https://${var.keystone_api_host}
+#         OIDCOAuthVerifyJwksUri https://vexxhost.us.auth0.com/.well-known/jwks.json
+
+#         OIDCRedirectURI https://${var.keystone_api_host}/v3/auth/OS-FEDERATION/identity_providers/redirect
+#         <Location /v3/auth/OS-FEDERATION/identity_providers/redirect>
+#             AuthType openid-connect
+#             Require valid-user
+#         </Location>
+#         <Location /v3/auth/OS-FEDERATION/websso/openid>
+#             AuthType openid-connect
+#             Require valid-user
+#         </Location>
+
+#     %{for name, config in var.keystone_openid_connect_idps}
+#         <Location /v3/auth/OS-FEDERATION/identity_providers/${name}/protocols/openid/websso>
+#             OIDCDiscoverURL https://${var.keystone_api_host}/v3/auth/OS-FEDERATION/identity_providers/redirect?iss=${urlencode(config.issuer)}
+#             AuthType openid-connect
+#             Require valid-user
+#         </Location>
+#         <Location /v3/OS-FEDERATION/identity_providers/${name}/protocols/openid/auth>
+#             LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so
+#             Header set Access-Control-Allow-Headers "Authorization,Content-Type"
+#             Header set Access-Control-Allow-Origin "*"
+#             AuthType oauth20
+#             Require valid-user
+#         </Location>
+#     %{endfor}
+#     </VirtualHost>
+#     EOT
+#   }
+# }),
diff --git a/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml b/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml
new file mode 100644
index 0000000..5091f6f
--- /dev/null
+++ b/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml
@@ -0,0 +1,101 @@
+# Copyright (c) 2022 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.
+
+- name: Get the IP address for the legacy MariaDB service
+  kubernetes.core.k8s_info:
+    api_version: v1
+    kind: Service
+    name: mariadb
+    namespace: openstack
+  register: _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_service
+  when: _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_ip is undefined
+
+- name: Get the IP address for the new Percona XtraDB service
+  kubernetes.core.k8s_info:
+    api_version: v1
+    kind: Service
+    name: percona-xtradb-haproxy
+    namespace: openstack
+  register: _openstack_helm_migrate_to_percona_xtradb_cluster_service
+  when: _openstack_helm_migrate_to_percona_xtradb_cluster_ip is undefined
+
+- name: Get current values for Helm chart & fail if it already points to Percona XtraDB Cluster
+  kubernetes.core.helm_info:
+    name: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_release_name }}"
+    release_namespace: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_release_namespace }}"
+  register: _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info
+  failed_when: _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info.status['values']['endpoints']['oslo_db'].get('hosts', {}).get('default', '') == 'percona-xtradb-haproxy'
+
+- name: Set facts for database endpoints
+  ansible.builtin.set_fact:
+    _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_ip: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_service.resources[0]['spec']['clusterIP'] }}"
+    _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_password: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info.status['values']['endpoints']['oslo_db']['auth']['admin']['password'] }}"
+    _openstack_helm_migrate_to_percona_xtradb_cluster_ip: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_service.resources[0]['spec']['clusterIP'] }}"
+    _openstack_helm_migrate_to_percona_xtradb_cluster_password: "{{ openstack_helm_endpoints['oslo_db']['auth']['admin']['password'] }}"
+
+- name: Ensure PyMySQL packages are installed
+  ansible.builtin.pip:
+    name: PyMySQL
+
+- name: Check if database already exists & fail if it already exists
+  community.mysql.mysql_db:
+    login_host: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_ip }}"
+    login_user: root
+    login_password: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_password }}"
+    name: "{{ item }}"
+    state: present
+  check_mode: true
+  register: _openstack_helm_migrate_to_percona_xtradb_cluster_db_check
+  failed_when: _openstack_helm_migrate_to_percona_xtradb_cluster_db_check is not changed
+  loop: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_databases }}"
+
+- name: Scale down replicas to 0 for database facing services
+  kubernetes.core.k8s_scale:
+    api_version: v1
+    kind: "{{ item.kind }}"
+    name: "{{ item.name }}"
+    namespace: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_release_namespace }}"
+    replicas: 0
+  loop: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_services }}"
+
+- name: Create temporary file for database dump
+  ansible.builtin.tempfile:
+    state: file
+    prefix: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_release_name }}"
+    suffix: .sql
+  register: _openstack_helm_migrate_to_percona_xtradb_cluster_file
+
+- name: Dump all of the databases to the local system
+  community.mysql.mysql_db:
+    login_host: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_ip }}"
+    login_user: root
+    login_password: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_password }}"
+    name: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_databases }}"
+    state: dump
+    target: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_file.path }}"
+    skip_lock_tables: true
+    dump_extra_args: --skip-add-locks
+  async: 7200
+  poll: 5
+
+- name: Import databases to the new Percona XtraDB Cluster
+  community.mysql.mysql_db:
+    login_host: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_ip }}"
+    login_user: root
+    login_password: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_password }}"
+    name: "{{ (openstack_helm_migrate_to_percona_xtradb_cluster_databases | length > 1) | ternary('all', openstack_helm_migrate_to_percona_xtradb_cluster_databases) }}"
+    state: import
+    target: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_file.path }}"
+  async: 7200
+  poll: 5
diff --git a/roles/openstack_helm_neutron/defaults/main.yml b/roles/openstack_helm_neutron/defaults/main.yml
new file mode 100644
index 0000000..e81f9f8
--- /dev/null
+++ b/roles/openstack_helm_neutron/defaults/main.yml
@@ -0,0 +1,25 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_neutron_chart_repo_name: openstack-helm
+openstack_helm_neutron_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_neutron_chart_name: neutron
+
+openstack_helm_neutron_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_neutron_image_tag: 18.2.1.dev7-6
+openstack_helm_neutron_heat_image_tag: wallaby
+
+openstack_helm_neutron_values: {}
+
+openstack_helm_neutron_networks: []
diff --git a/roles/openstack_helm_neutron/meta/main.yml b/roles/openstack_helm_neutron/meta/main.yml
new file mode 100644
index 0000000..6c6c7e1
--- /dev/null
+++ b/roles/openstack_helm_neutron/meta/main.yml
@@ -0,0 +1,26 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_neutron_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_neutron_chart_repo_url }}"
+  - cilium
+  - openstack_namespace
+  - percona_xtradb_cluster
+  - openstack_helm_infra_memcached
+  - openstack_helm_infra_rabbitmq
+  - openstack_helm_infra_openvswitch
+  - openstack_helm_keystone
diff --git a/roles/openstack_helm_neutron/tasks/main.yml b/roles/openstack_helm_neutron/tasks/main.yml
new file mode 100644
index 0000000..bca4586
--- /dev/null
+++ b/roles/openstack_helm_neutron/tasks/main.yml
@@ -0,0 +1,87 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_neutron_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_neutron_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_neutron_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_neutron_chart_name }}"
+    chart_ref: "{{ openstack_helm_neutron_chart_repo_name }}/{{ openstack_helm_neutron_chart_name }}"
+    chart_version: 0.2.11
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_neutron_values | combine(openstack_helm_neutron_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: network
+    openstack_helm_ingress_service_name: neutron-server
+    openstack_helm_ingress_service_port: 9696
+
+- name: Create networks
+  openstack.cloud.network:
+    auth:
+      auth_url: "https://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}"
+      username: "{{ openstack_helm_endpoints['identity']['auth']['admin']['username'] }}"
+      password: "{{ openstack_helm_endpoints['identity']['auth']['admin']['password'] }}"
+      project_name: admin
+      user_domain_name: Default
+      project_domain_name: Default
+    region_name: "{{ openstack_helm_endpoints['identity']['auth']['neutron']['region_name'] }}"
+    # Network settings
+    name: "{{ item.name }}"
+    external: "{{ item.external | default(omit) }}"
+    shared: "{{ item.shared | default(omit) }}"
+    mtu_size: "{{ item.mtu_size | default(omit) }}"
+    port_security_enabled: "{{ item.port_security_enabled | default(omit) }}"
+    provider_network_type: "{{ item.provider_network_type | default(omit) }}"
+    provider_physical_network: "{{ item.provider_physical_network | default(omit) }}"
+    provider_segmentation_id: "{{ item.provider_segmentation_id | default(omit) }}"
+  loop: "{{ openstack_helm_neutron_networks }}"
+
+- name: Create subnets
+  openstack.cloud.subnet:
+    auth:
+      auth_url: "https://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}"
+      username: "{{ openstack_helm_endpoints['identity']['auth']['admin']['username'] }}"
+      password: "{{ openstack_helm_endpoints['identity']['auth']['admin']['password'] }}"
+      project_name: admin
+      user_domain_name: Default
+      project_domain_name: Default
+    region_name: "{{ openstack_helm_endpoints['identity']['auth']['neutron']['region_name'] }}"
+    # Subnet settings
+    network_name: "{{ item.0.name }}"
+    name: "{{ item.1.name }}"
+    ip_version: "{{ item.1.ip_version | default(omit) }}"
+    cidr: "{{ item.1.cidr | default(omit) }}"
+    gateway_ip: "{{ item.1.gateway_ip | default(omit) }}"
+    no_gateway_ip: "{{ item.1.no_gateway_ip | default(omit) }}"
+    allocation_pool_start: "{{ item.1.allocation_pool_start | default(omit) }}"
+    allocation_pool_end: "{{ item.1.allocation_pool_end | default(omit) }}"
+    dns_nameservers: "{{ item.1.dns_nameservers | default(omit) }}"
+    enable_dhcp: "{{ item.1.enable_dhcp | default(omit) }}"
+    host_routes: "{{ item.1.host_routes | default(omit) }}"
+    ipv6_address_mode: "{{ item.1.ipv6_address_mode | default(omit) }}"
+    ipv6_ra_mode: "{{ item.1.ipv6_ra_mode | default(omit) }}"
+  with_subelements:
+    - "{{ openstack_helm_neutron_networks }}"
+    - subnets
diff --git a/roles/openstack_helm_neutron/vars/main.yml b/roles/openstack_helm_neutron/vars/main.yml
new file mode 100644
index 0000000..d30456c
--- /dev/null
+++ b/roles/openstack_helm_neutron/vars/main.yml
@@ -0,0 +1,82 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_neutron_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_neutron_image_repository }}/heat:{{ openstack_helm_neutron_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_neutron_image_repository }}/heat:{{ openstack_helm_neutron_heat_image_tag }}"
+      db_init: "{{ openstack_helm_neutron_image_repository }}/heat:{{ openstack_helm_neutron_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_neutron_image_repository }}/kubernetes-entrypoint:latest"
+      ks_endpoints: "{{ openstack_helm_neutron_image_repository }}/heat:{{ openstack_helm_neutron_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_neutron_image_repository }}/heat:{{ openstack_helm_neutron_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_neutron_image_repository }}/heat:{{ openstack_helm_neutron_heat_image_tag }}"
+      neutron_bagpipe_bgp: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_db_sync: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_dhcp: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_ironic_agent: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_l2gw: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_l3: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_linuxbridge_agent: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_metadata: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_netns_cleanup_cron: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_openvswitch_agent: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_server: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_sriov_agent_init: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      neutron_sriov_agent: "{{ openstack_helm_neutron_image_repository }}/neutron:{{ openstack_helm_neutron_image_tag }}"
+      rabbit_init: "{{ openstack_helm_neutron_image_repository }}/rabbitmq:3.8.23-management"
+  pod:
+    replicas:
+      server: 3
+  conf:
+    paste:
+      composite:neutronapi_v2_0:
+        keystone: cors http_proxy_to_wsgi request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0
+    neutron:
+      DEFAULT:
+        api_workers: 8
+        dhcp_agents_per_network: 3
+        log_config_append: null
+        rpc_workers: 8
+        service_plugins: router,vpnaas
+      cors:
+        allowed_origin: "*"
+      service_providers:
+        service_provider: VPN:strongswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default
+    dhcp_agent:
+      DEFAULT:
+        dnsmasq_dns_servers: 1.1.1.1
+        enable_isolated_metadata: true
+    l3_agent:
+      AGENT:
+        extensions: vpnaas
+      vpnagent:
+        vpn_device_driver: neutron_vpnaas.services.vpn.device_drivers.strongswan_ipsec.StrongSwanDriver
+    metadata_agent:
+      DEFAULT:
+        nova_metadata_port: 8776
+        metadata_proxy_shared_secret: "{{ openstack_helm_endpoints['compute_metadata']['secret'] }}"
+    plugins:
+      ml2_conf:
+        ml2:
+          extension_drivers: port_security,dns
+          type_drivers: flat,gre,vlan,vxlan
+        ml2_type_gre:
+          tunnel_id_ranges: 1:1000
+        ml2_type_vlan:
+          network_vlan_ranges: external:1:4094
+  manifests:
+    ingress_server: false
+    service_ingress_server: false
diff --git a/roles/openstack_helm_nova/defaults/main.yml b/roles/openstack_helm_nova/defaults/main.yml
new file mode 100644
index 0000000..75ad162
--- /dev/null
+++ b/roles/openstack_helm_nova/defaults/main.yml
@@ -0,0 +1,29 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_nova_chart_repo_name: openstack-helm
+openstack_helm_nova_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_nova_chart_name: nova
+
+openstack_helm_nova_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_nova_image_tag: 23.1.1.dev11
+openstack_helm_nova_ssh_image_tag: wallaby
+openstack_helm_nova_heat_image_tag: wallaby
+
+openstack_helm_nova_diff: false
+openstack_helm_nova_migrate_from_mariadb: false
+
+openstack_helm_nova_values: {}
+
+openstack_helm_nova_flavors: []
diff --git a/roles/openstack_helm_nova/meta/main.yml b/roles/openstack_helm_nova/meta/main.yml
new file mode 100644
index 0000000..6a725f3
--- /dev/null
+++ b/roles/openstack_helm_nova/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_nova_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_nova_chart_repo_url }}"
diff --git a/roles/openstack_helm_nova/tasks/main.yml b/roles/openstack_helm_nova/tasks/main.yml
new file mode 100644
index 0000000..db1f2eb
--- /dev/null
+++ b/roles/openstack_helm_nova/tasks/main.yml
@@ -0,0 +1,104 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_nova_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_nova_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_nova_chart_name }}"
+
+- name: Generate Helm values comparison
+  ansible.builtin.include_role:
+    name: helm_diff
+  vars:
+    helm_diff_release_name: "{{ openstack_helm_nova_chart_name }}"
+    helm_diff_release_namespace: openstack
+    helm_diff_values: "{{ _openstack_helm_nova_values }}"
+  when:
+    - openstack_helm_nova_diff | bool
+
+- name: Migrate database from MariaDB to Percona XtraDB Cluster
+  ansible.builtin.include_role:
+    name: openstack_helm_migrate_to_percona_xtradb_cluster
+  vars:
+    openstack_helm_migrate_to_percona_xtradb_cluster_release_name: "{{ openstack_helm_nova_chart_name }}"
+    openstack_helm_migrate_to_percona_xtradb_cluster_release_namespace: openstack
+    openstack_helm_migrate_to_percona_xtradb_cluster_databases:
+      - nova
+      - nova_api
+      - nova_cell0
+    openstack_helm_migrate_to_percona_xtradb_cluster_services:
+      - kind: Deployment
+        name: nova-api-metadata
+      - kind: Deployment
+        name: nova-api-osapi
+      - kind: Deployment
+        name: nova-conductor
+      - kind: Deployment
+        name: nova-scheduler
+  when:
+    - openstack_helm_nova_migrate_from_mariadb | bool
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_nova_chart_name }}"
+    chart_ref: "{{ openstack_helm_nova_chart_repo_name }}/{{ openstack_helm_nova_chart_name }}"
+    chart_version: 0.2.30
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_nova_values }}"
+    # NOTE(mnaser): This is a a workaround due to the fact that Nova's online
+    #               data migrations take forever.
+    wait_timeout: 10m
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: compute
+    openstack_helm_ingress_service_name: nova-api
+    openstack_helm_ingress_service_port: 8774
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: compute_novnc_proxy
+    openstack_helm_ingress_service_name: nova-novncproxy
+    openstack_helm_ingress_service_port: 6080
+
+- name: Create flavors
+  openstack.cloud.compute_flavor:
+    auth:
+      auth_url: "https://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}"
+      username: "{{ openstack_helm_endpoints['identity']['auth']['admin']['username'] }}"
+      password: "{{ openstack_helm_endpoints['identity']['auth']['admin']['password'] }}"
+      project_name: admin
+      user_domain_name: Default
+      project_domain_name: Default
+    region_name: "{{ openstack_helm_endpoints['identity']['auth']['neutron']['region_name'] }}"
+    # Flavor settings
+    flavorid: "{{ item.flavorid | default(omit) }}"
+    name: "{{ item.name }}"
+    vcpus: "{{ item.vcpus }}"
+    ram: "{{ item.ram }}"
+    disk: "{{ item.disk | default(omit) }}"
+    ephemeral: "{{ item.ephemeral | default(omit) }}"
+    swap: "{{ item.swap | default(omit) }}"
+    is_public: "{{ item.is_public | default(omit) }}"
+    rxtx_factor: "{{ item.rxtx_factor | default(omit) }}"
+    extra_specs: "{{ item.extra_specs | default(omit) }}"
+  loop: "{{ openstack_helm_nova_flavors }}"
diff --git a/roles/openstack_helm_nova/vars/main.yml b/roles/openstack_helm_nova/vars/main.yml
new file mode 100644
index 0000000..bc22c7c
--- /dev/null
+++ b/roles/openstack_helm_nova/vars/main.yml
@@ -0,0 +1,117 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_nova_values: "{{ __openstack_helm_nova_values | combine(openstack_helm_nova_values, recursive=True) }}"
+__openstack_helm_nova_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  labels:
+    agent:
+      compute_ironic:
+        node_selector_key: openstack-control-plane
+        node_selector_value: enabled
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      db_init: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_nova_image_repository }}/kubernetes-entrypoint:latest"
+      ks_endpoints: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      nova_api: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_archive_deleted_rows: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_cell_setup_init: "{{ openstack_helm_nova_image_repository }}/heat:{{ openstack_helm_nova_heat_image_tag }}"
+      nova_cell_setup: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      # TODO(mnaser): Fix Ironic images
+      nova_compute_ironic: "docker.io/kolla/ubuntu-source-nova-compute-ironic:wallaby"
+      nova_compute_ssh: "{{ openstack_helm_nova_image_repository }}/nova-ssh:{{ openstack_helm_nova_ssh_image_tag }}"
+      nova_compute: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_conductor: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_consoleauth: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_db_sync: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_novncproxy_assets: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_novncproxy: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_placement: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_scheduler: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_service_cleaner: "{{ openstack_helm_nova_image_repository }}/cli:latest"
+      nova_spiceproxy_assets: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      nova_spiceproxy: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
+      rabbit_init: "{{ openstack_helm_nova_image_repository }}/rabbitmq:3.8.23-management"
+  bootstrap:
+    structured:
+      flavors:
+        enabled: false
+  pod:
+    replicas:
+      api_metadata: 3
+      osapi: 3
+      conductor: 3
+      scheduler: 3
+      novncproxy: 3
+      spiceproxy: 3
+  conf:
+    paste:
+      composite:openstack_compute_api_v21:
+        keystone: cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21
+      composite:openstack_compute_api_v21_legacy_v2_compatible:
+        keystone: cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21
+    nova:
+      DEFAULT:
+        cpu_allocation_ratio: 4.5
+        ram_allocation_ratio: 0.9
+        disk_allocation_ratio: 3.0
+        resume_guests_state_on_host_boot: true
+        osapi_compute_workers: 8
+        metadata_workers: 8
+      cache:
+        backend: oslo_cache.memcache_pool
+      cinder:
+        catalog_info: volumev3::internalURL
+      conductor:
+        workers: 8
+      cors:
+        allowed_origin: "*"
+        allow_headers: "X-Auth-Token,X-OpenStack-Nova-API-Version"
+      filter_scheduler:
+        enabled_filters: ComputeFilter, AggregateTypeAffinityFilter, ComputeCapabilitiesFilter, PciPassthroughFilter, ImagePropertiesFilter, ServerGroupAntiAffinityFilter, ServerGroupAffinityFilter
+        image_properties_default_architecture: x86_64
+        max_instances_per_host: 200
+      glance:
+        enable_rbd_download: true
+      neutron:
+        metadata_proxy_shared_secret: "{{ openstack_helm_endpoints['compute_metadata']['secret'] }}"
+      scheduler:
+        workers: 8
+    nova_ironic:
+      DEFAULT:
+        log_config_append: null
+        force_config_drive: true
+  manifests:
+    deployment_consoleauth: false
+    deployment_placement: false
+    ingress_metadata: false
+    ingress_novncproxy: false
+    ingress_osapi: false
+    ingress_placement: false
+    job_db_init_placement: false
+    job_ks_placement_endpoints: false
+    job_ks_placement_service: false
+    job_ks_placement_user: false
+    secret_keystone_placement: false
+    service_ingress_metadata: false
+    service_ingress_novncproxy: false
+    service_ingress_osapi: false
+    service_ingress_placement: false
+    service_placement: false
+    statefulset_compute_ironic: true
diff --git a/roles/openstack_helm_placement/defaults/main.yml b/roles/openstack_helm_placement/defaults/main.yml
new file mode 100644
index 0000000..113b39b
--- /dev/null
+++ b/roles/openstack_helm_placement/defaults/main.yml
@@ -0,0 +1,23 @@
+# Copyright (c) 2022 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.
+
+openstack_helm_placement_chart_repo_name: openstack-helm
+openstack_helm_placement_chart_repo_url: https://tarballs.opendev.org/openstack/openstack-helm/
+openstack_helm_placement_chart_name: placement
+
+openstack_helm_placement_image_repository: "{{ atmosphere_image_repository | default('us-docker.pkg.dev/vexxhost-infra/openstack') }}"
+openstack_helm_placement_image_tag: 5.0.1
+openstack_helm_placement_heat_image_tag: wallaby
+
+openstack_helm_placement_values: {}
diff --git a/roles/openstack_helm_placement/meta/main.yml b/roles/openstack_helm_placement/meta/main.yml
new file mode 100644
index 0000000..c0fb81a
--- /dev/null
+++ b/roles/openstack_helm_placement/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: "{{ openstack_helm_placement_chart_repo_name }}"
+      helm_repository_repo_url: "{{ openstack_helm_placement_chart_repo_url }}"
diff --git a/roles/openstack_helm_placement/tasks/main.yml b/roles/openstack_helm_placement/tasks/main.yml
new file mode 100644
index 0000000..6c39459
--- /dev/null
+++ b/roles/openstack_helm_placement/tasks/main.yml
@@ -0,0 +1,38 @@
+# Copyright (c) 2022 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.
+
+- name: Generate OpenStack-Helm endpoints
+  ansible.builtin.include_role:
+    name: openstack_helm_endpoints
+  vars:
+    openstack_helm_endpoints_repo_name: "{{ openstack_helm_placement_chart_repo_name }}"
+    openstack_helm_endpoints_repo_url: "{{ openstack_helm_placement_chart_repo_url }}"
+    openstack_helm_endpoints_chart: "{{ openstack_helm_placement_chart_name }}"
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: "{{ openstack_helm_placement_chart_name }}"
+    chart_ref: "{{ openstack_helm_placement_chart_repo_name }}/{{ openstack_helm_placement_chart_name }}"
+    chart_version: 0.2.5
+    release_namespace: openstack
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _openstack_helm_placement_values | combine(openstack_helm_placement_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: placement
+    openstack_helm_ingress_service_name: placement-api
+    openstack_helm_ingress_service_port: 8778
diff --git a/roles/openstack_helm_placement/vars/main.yml b/roles/openstack_helm_placement/vars/main.yml
new file mode 100644
index 0000000..cd47f82
--- /dev/null
+++ b/roles/openstack_helm_placement/vars/main.yml
@@ -0,0 +1,38 @@
+# Copyright (c) 2022 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.
+
+_openstack_helm_placement_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags:
+      bootstrap: "{{ openstack_helm_placement_image_repository }}/heat:{{ openstack_helm_placement_heat_image_tag }}"
+      db_drop: "{{ openstack_helm_placement_image_repository }}/heat:{{ openstack_helm_placement_heat_image_tag }}"
+      db_init: "{{ openstack_helm_placement_image_repository }}/heat:{{ openstack_helm_placement_heat_image_tag }}"
+      dep_check: "{{ openstack_helm_placement_image_repository }}/kubernetes-entrypoint:latest"
+      ks_endpoints: "{{ openstack_helm_placement_image_repository }}/heat:{{ openstack_helm_placement_heat_image_tag }}"
+      ks_service: "{{ openstack_helm_placement_image_repository }}/heat:{{ openstack_helm_placement_heat_image_tag }}"
+      ks_user: "{{ openstack_helm_placement_image_repository }}/heat:{{ openstack_helm_placement_heat_image_tag }}"
+      placement_db_sync: "{{ openstack_helm_placement_image_repository }}/placement:{{ openstack_helm_placement_image_tag }}"
+      placement: "{{ openstack_helm_placement_image_repository }}/placement:{{ openstack_helm_placement_image_tag }}"
+      rabbit_init: "{{ openstack_helm_placement_image_repository }}/rabbitmq:3.8.23-management"
+  pod:
+    replicas:
+      api: 3
+  conf:
+    placement:
+      DEFAULT:
+        log_config_append: null
+  manifests:
+    ingress: false
+    service_ingress: false
diff --git a/roles/openstack_namespace/tasks/main.yml b/roles/openstack_namespace/tasks/main.yml
new file mode 100644
index 0000000..a864a09
--- /dev/null
+++ b/roles/openstack_namespace/tasks/main.yml
@@ -0,0 +1,22 @@
+# Copyright (c) 2022 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.
+
+- name: Create namespace
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: v1
+      kind: Namespace
+      metadata:
+        name: openstack
diff --git a/roles/percona_xtradb_cluster/meta/main.yml b/roles/percona_xtradb_cluster/meta/main.yml
new file mode 100644
index 0000000..6b1f74e
--- /dev/null
+++ b/roles/percona_xtradb_cluster/meta/main.yml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: percona
+      helm_repository_repo_url: https://percona.github.io/percona-helm-charts/
diff --git a/roles/percona_xtradb_cluster/tasks/main.yml b/roles/percona_xtradb_cluster/tasks/main.yml
new file mode 100644
index 0000000..81c35cd
--- /dev/null
+++ b/roles/percona_xtradb_cluster/tasks/main.yml
@@ -0,0 +1,180 @@
+# Copyright (c) 2022 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.
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: pxc-operator
+    chart_ref: percona/pxc-operator
+    chart_version: 1.10.0
+    release_namespace: openstack
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+
+- name: Deploy cluster
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: pxc.percona.com/v1-10-0
+      kind: PerconaXtraDBCluster
+      metadata:
+        name: percona-xtradb
+        namespace: openstack
+      spec:
+        crVersion: 1.10.0
+        secretsName: percona-xtradb
+        pxc:
+          size: 3
+          image: percona/percona-xtradb-cluster:5.7.36-31.55
+          autoRecovery: true
+          configuration: |
+            [mysqld]
+            max_connections=8192
+          sidecars:
+            - name: exporter
+              image: quay.io/prometheus/mysqld-exporter:v0.14.0
+              ports:
+                - name: metrics
+                  containerPort: 9104
+              livenessProbe:
+                httpGet:
+                  path: /
+                  port: 9104
+              env:
+                - name: MONITOR_PASSWORD
+                  valueFrom:
+                    secretKeyRef:
+                      name: percona-xtradb
+                      key: monitor
+                - name: DATA_SOURCE_NAME
+                  value: "monitor:$(MONITOR_PASSWORD)@(localhost:3306)/"
+          nodeSelector:
+            openstack-control-plane: enabled
+          volumeSpec:
+            persistentVolumeClaim:
+              resources:
+                requests:
+                  storage: 160Gi
+        haproxy:
+          enabled: true
+          size: 3
+          image: percona/percona-xtradb-cluster-operator:1.10.0-haproxy
+          nodeSelector:
+            openstack-control-plane: enabled
+    wait: true
+    wait_timeout: 600
+    wait_condition:
+      type: "ready"
+      status: "True"
+
+- name: Create PodMonitor
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: monitoring.coreos.com/v1
+      kind: PodMonitor
+      metadata:
+        name: percona-xtradb-pxc
+        namespace: monitoring
+        labels:
+          release: kube-prometheus-stack
+      spec:
+        jobLabel: app.kubernetes.io/component
+        podMetricsEndpoints:
+          - port: metrics
+            path: /metrics
+            relabelings:
+              - sourceLabels: ["__meta_kubernetes_pod_name"]
+                targetLabel: "instance"
+              - action: "labeldrop"
+                regex: "^(container|endpoint|namespace|pod|service)$"
+        namespaceSelector:
+          matchNames:
+            - openstack
+        selector:
+          matchLabels:
+            app.kubernetes.io/component: pxc
+            app.kubernetes.io/instance: percona-xtradb
+
+- name: Create PrometheusRule
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: monitoring.coreos.com/v1
+      kind: PrometheusRule
+      metadata:
+        name: percona-xtradb-pxc
+        namespace: monitoring
+        labels:
+          release: kube-prometheus-stack
+      spec:
+        groups:
+          # TODO: basic rules
+          - name: general
+            rules:
+              - alert: MySQLDown
+                expr: mysql_up != 1
+                for: 5m
+                labels:
+                  severity: critical
+              - alert: MysqlTooManyConnections
+                expr: max_over_time(mysql_global_status_threads_connected[1m]) / mysql_global_variables_max_connections * 100 > 80
+                for: 2m
+                labels:
+                  severity: warning
+              - alert: MysqlHighThreadsRunning
+                expr: max_over_time(mysql_global_status_threads_running[1m]) / mysql_global_variables_max_connections * 100 > 60
+                for: 2m
+                labels:
+                  severity: warning
+              - alert: MysqlSlowQueries
+                expr: increase(mysql_global_status_slow_queries[1m]) > 0
+                for: 2m
+                labels:
+                  severity: warning
+          - name: galera
+            rules:
+              - alert: MySQLGaleraNotReady
+                expr: mysql_global_status_wsrep_ready != 1
+                for: 5m
+                labels:
+                  severity: critical
+              - alert: MySQLGaleraOutOfSync
+                expr: mysql_global_status_wsrep_local_state != 4 and mysql_global_variables_wsrep_desync == 0
+                for: 5m
+                labels:
+                  severity: critical
+              - alert: MySQLGaleraDonorFallingBehind
+                expr: mysql_global_status_wsrep_local_state == 2 and mysql_global_status_wsrep_local_recv_queue > 100
+                for: 5m
+                labels:
+                  severity: warning
+              - alert: MySQLReplicationNotRunning
+                expr: mysql_slave_status_slave_io_running == 0 or mysql_slave_status_slave_sql_running == 0
+                for: 2m
+                labels:
+                  severity: critical
+              - alert: MySQLReplicationLag
+                expr: (instance:mysql_slave_lag_seconds > 30) and on(instance) (predict_linear(instance:mysql_slave_lag_seconds[5m], 60 * 2) > 0)
+                for: 1m
+                labels:
+                  severity: critical
+              - alert: MySQLHeartbeatLag
+                expr: (instance:mysql_heartbeat_lag_seconds > 30) and on(instance) (predict_linear(instance:mysql_heartbeat_lag_seconds[5m], 60 * 2) > 0)
+                for: 1m
+                labels:
+                  severity: critical
+              - alert: MySQLInnoDBLogWaits
+                expr: rate(mysql_global_status_innodb_log_waits[15m]) > 10
+                labels:
+                  severity: warning
diff --git a/roles/prometheus_pushgateway/meta/main.yml b/roles/prometheus_pushgateway/meta/main.yml
new file mode 100644
index 0000000..8da9c00
--- /dev/null
+++ b/roles/prometheus_pushgateway/meta/main.yml
@@ -0,0 +1,21 @@
+# Copyright (c) 2022 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.
+
+dependencies:
+  - role: helm_repository
+    vars:
+      helm_repository_name: prometheus-community
+      helm_repository_repo_url: https://prometheus-community.github.io/helm-charts
+  - cilium
+  - kube_prometheus_stack
diff --git a/roles/prometheus_pushgateway/tasks/main.yml b/roles/prometheus_pushgateway/tasks/main.yml
new file mode 100644
index 0000000..5042914
--- /dev/null
+++ b/roles/prometheus_pushgateway/tasks/main.yml
@@ -0,0 +1,34 @@
+# Copyright (c) 2022 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.
+
+- name: Deploy Helm chart
+  kubernetes.core.helm:
+    name: prometheus-pushgateway
+    chart_ref: prometheus-community/prometheus-pushgateway
+    chart_version: 1.16.0
+    release_namespace: monitoring
+    kubeconfig: /etc/kubernetes/admin.conf
+    values:
+      nodeSelector:
+        openstack-control-plane: enabled
+      serviceMonitor:
+        enabled: true
+        namespace: monitoring
+        additionalLabels:
+          release: kube-prometheus-stack
+        relabelings:
+          - sourceLabels: ["__meta_kubernetes_pod_name"]
+            targetLabel: "instance"
+          - regex: "^(container|endpoint|namespace|pod|service)$"
+            action: "labeldrop"