From 2d1a1b1df65fead8d5d08f9c9d170e0281e852ba Mon Sep 17 00:00:00 2001
From: chenxing <chason.chan@foxmail.com>
Date: Sat, 11 Feb 2017 17:01:19 +0000
Subject: [PATCH] Add vitrage ansible role

Co-Authored-By: caoyuan <cao.yuan@99cloud.net>
Co-Authored-By: Eduardo Gonzalez <dabarren@gmail.com>

Depends-on I7ffb991adbd05275e331401cd73fc84362084ebd
Change-Id: I85afeb5dfce0bf6350a444000bdb885c6fe079c9
Partially-implements: blueprint vitrage-roles
---
 ansible/group_vars/all.yml                    |   3 +
 ansible/inventory/all-in-one                  |  19 +++
 ansible/inventory/multinode                   |  19 +++
 ansible/roles/common/tasks/config.yml         |   1 +
 .../conf/filter/01-rewrite-0.12.conf.j2       |   3 +-
 .../conf/filter/01-rewrite-0.14.conf.j2       |   7 +-
 .../templates/cron-logrotate-vitrage.conf.j2  |   3 +
 ansible/roles/common/templates/cron.json.j2   |   1 +
 ansible/roles/haproxy/tasks/precheck.yml      |  11 ++
 .../roles/haproxy/templates/haproxy.cfg.j2    |  19 +++
 ansible/roles/vitrage/defaults/main.yml       | 129 ++++++++++++++++++
 ansible/roles/vitrage/handlers/main.yml       | 115 ++++++++++++++++
 ansible/roles/vitrage/meta/main.yml           |   3 +
 ansible/roles/vitrage/tasks/bootstrap.yml     |  33 +++++
 .../roles/vitrage/tasks/bootstrap_service.yml |  19 +++
 ansible/roles/vitrage/tasks/config.yml        | 100 ++++++++++++++
 ansible/roles/vitrage/tasks/deploy.yml        |  16 +++
 ansible/roles/vitrage/tasks/main.yml          |   2 +
 ansible/roles/vitrage/tasks/precheck.yml      |  16 +++
 ansible/roles/vitrage/tasks/pull.yml          |  10 ++
 ansible/roles/vitrage/tasks/reconfigure.yml   |   2 +
 ansible/roles/vitrage/tasks/register.yml      |  42 ++++++
 ansible/roles/vitrage/tasks/upgrade.yml       |   7 +
 .../vitrage/templates/vitrage-api.json.j2     |  34 +++++
 .../templates/vitrage-collector.json.j2       |  25 ++++
 .../vitrage/templates/vitrage-graph.json.j2   |  25 ++++
 .../vitrage/templates/vitrage-ml.json.j2      |  25 ++++
 .../templates/vitrage-notifier.json.j2        |  25 ++++
 .../roles/vitrage/templates/vitrage.conf.j2   |  72 ++++++++++
 .../vitrage/templates/wsgi-vitrage.conf.j2    |  25 ++++
 ansible/site.yml                              |  14 ++
 etc/kolla/globals.yml                         |   1 +
 etc/kolla/passwords.yml                       |   3 +
 .../notes/add-vitrage-6b8da2c81a68b01b.yaml   |   3 +
 34 files changed, 830 insertions(+), 2 deletions(-)
 create mode 100644 ansible/roles/common/templates/cron-logrotate-vitrage.conf.j2
 create mode 100644 ansible/roles/vitrage/defaults/main.yml
 create mode 100644 ansible/roles/vitrage/handlers/main.yml
 create mode 100644 ansible/roles/vitrage/meta/main.yml
 create mode 100644 ansible/roles/vitrage/tasks/bootstrap.yml
 create mode 100644 ansible/roles/vitrage/tasks/bootstrap_service.yml
 create mode 100644 ansible/roles/vitrage/tasks/config.yml
 create mode 100644 ansible/roles/vitrage/tasks/deploy.yml
 create mode 100644 ansible/roles/vitrage/tasks/main.yml
 create mode 100644 ansible/roles/vitrage/tasks/precheck.yml
 create mode 100644 ansible/roles/vitrage/tasks/pull.yml
 create mode 100644 ansible/roles/vitrage/tasks/reconfigure.yml
 create mode 100644 ansible/roles/vitrage/tasks/register.yml
 create mode 100644 ansible/roles/vitrage/tasks/upgrade.yml
 create mode 100644 ansible/roles/vitrage/templates/vitrage-api.json.j2
 create mode 100644 ansible/roles/vitrage/templates/vitrage-collector.json.j2
 create mode 100644 ansible/roles/vitrage/templates/vitrage-graph.json.j2
 create mode 100644 ansible/roles/vitrage/templates/vitrage-ml.json.j2
 create mode 100644 ansible/roles/vitrage/templates/vitrage-notifier.json.j2
 create mode 100644 ansible/roles/vitrage/templates/vitrage.conf.j2
 create mode 100644 ansible/roles/vitrage/templates/wsgi-vitrage.conf.j2
 create mode 100644 releasenotes/notes/add-vitrage-6b8da2c81a68b01b.yaml

diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index 3d70edf63..d24df918f 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -317,6 +317,8 @@ opendaylight_openflow_port: "6653"
 opendaylight_ovsdb_port: "6641"
 opendaylight_haproxy_ovsdb_port: "6642"
 
+vitrage_api_port: "8999"
+
 public_protocol: "{{ 'https' if kolla_enable_tls_external | bool else 'http' }}"
 internal_protocol: "http"
 admin_protocol: "http"
@@ -457,6 +459,7 @@ enable_tacker: "no"
 enable_telegraf: "no"
 enable_tempest: "no"
 enable_trove: "no"
+enable_vitrage: "no"
 enable_vmtp: "no"
 enable_watcher: "no"
 enable_zun: "no"
diff --git a/ansible/inventory/all-in-one b/ansible/inventory/all-in-one
index 16d04f679..23cf78698 100644
--- a/ansible/inventory/all-in-one
+++ b/ansible/inventory/all-in-one
@@ -185,6 +185,9 @@ control
 [tacker:children]
 control
 
+[vitrage:children]
+control
+
 # Tempest
 [tempest:children]
 control
@@ -593,3 +596,19 @@ tacker
 
 [tacker-conductor:children]
 tacker
+
+# Vitrage
+[vitrage-api:children]
+vitrage
+
+[vitrage-notifier:children]
+vitrage
+
+[vitrage-graph:children]
+vitrage
+
+[vitrage-collector:children]
+vitrage
+
+[vitrage-ml:children]
+vitrage
diff --git a/ansible/inventory/multinode b/ansible/inventory/multinode
index 9dd45d6bc..533ec560a 100644
--- a/ansible/inventory/multinode
+++ b/ansible/inventory/multinode
@@ -216,6 +216,9 @@ control
 [vmtp:children]
 control
 
+[vitrage:children]
+control
+
 [watcher:children]
 control
 
@@ -611,3 +614,19 @@ tacker
 
 [tacker-conductor:children]
 tacker
+
+# Vitrage
+[vitrage-api:children]
+vitrage
+
+[vitrage-notifier:children]
+vitrage
+
+[vitrage-graph:children]
+vitrage
+
+[vitrage-collector:children]
+vitrage
+
+[vitrage-ml:children]
+vitrage
diff --git a/ansible/roles/common/tasks/config.yml b/ansible/roles/common/tasks/config.yml
index 1e89d48eb..e48b98fd0 100644
--- a/ansible/roles/common/tasks/config.yml
+++ b/ansible/roles/common/tasks/config.yml
@@ -193,6 +193,7 @@
     - { name: "tacker", enabled: "{{ enable_tacker }}" }
     - { name: "tempest", enabled: "{{ enable_tempest }}" }
     - { name: "trove", enabled: "{{ enable_trove }}" }
+    - { name: "vitrage", enabled: "{{ enable_vitrage }}" }
     - { name: "watcher", enabled: "{{ enable_watcher }}" }
     - { name: "zun", enabled: "{{ enable_zun }}" }
   notify:
diff --git a/ansible/roles/common/templates/conf/filter/01-rewrite-0.12.conf.j2 b/ansible/roles/common/templates/conf/filter/01-rewrite-0.12.conf.j2
index 8fcb7435d..d998ef630 100644
--- a/ansible/roles/common/templates/conf/filter/01-rewrite-0.12.conf.j2
+++ b/ansible/roles/common/templates/conf/filter/01-rewrite-0.12.conf.j2
@@ -2,7 +2,7 @@
     @type rewrite_tag_filter
     capitalize_regex_backreference yes
     rewriterule1 programname ^(cinder-api-access|cloudkitty-api-access|gnocchi-api-access|horizon-access|keystone-apache-admin-access|keystone-apache-public-access|placement-api-access|panko-api-access)$ apache_access
-    rewriterule2 programname ^(aodh_wsgi_access|barbican-api|zun_api_wsgi_access)$ wsgi_access
+    rewriterule2 programname ^(aodh_wsgi_access|barbican-api|zun_api_wsgi_access|vitrage_wsgi_access)$ wsgi_access
     rewriterule3 programname ^(nova-api|nova-compute|nova-compute-ironic|nova-conductor|nova-consoleauth|nova-manage|nova-novncproxy|nova-scheduler|nova-placement-api|placement-api|privsep-helper)$ openstack_python
     rewriterule4 programname ^(sahara-api|sahara-engine)$ openstack_python
     rewriterule5 programname ^(neutron-server|neutron-openvswitch-agent|neutron-ns-metadata-proxy|neutron-metadata-agent|neutron-l3-agent|neutron-dhcp-agent)$ openstack_python
@@ -31,4 +31,5 @@
     rewriterule28 programname ^(ironic-api|ironic-conductor)$ openstack_python
     rewriterule29 programname ^(panko-api|panko-dbsync)$ openstack_python
     rewriterule30 programname ^(tacker-server|tacker-conductor)$ openstack_python
+    rewriterule31 programname ^(vitrage-collector|vitrage-ml|vitrage-notifier|vitrage-graph)$ openstack_python
 </match>
diff --git a/ansible/roles/common/templates/conf/filter/01-rewrite-0.14.conf.j2 b/ansible/roles/common/templates/conf/filter/01-rewrite-0.14.conf.j2
index 30467357a..7d93201ce 100644
--- a/ansible/roles/common/templates/conf/filter/01-rewrite-0.14.conf.j2
+++ b/ansible/roles/common/templates/conf/filter/01-rewrite-0.14.conf.j2
@@ -8,7 +8,7 @@
   </rule>
   <rule>
     key     programname
-    pattern ^(aodh_wsgi_access|barbican-api|zun_api_wsgi_access)$
+    pattern ^(aodh_wsgi_access|barbican-api|zun_api_wsgi_access|vitrage_wsgi_access)$
     tag wsgi_access
   </rule>
   <rule>
@@ -151,4 +151,9 @@
     pattern ^(tacker-server|tacker-conductor)$
     tag openstack_python
   </rule>
+  <rule>
+    key     programname
+    pattern ^(vitrage-collector|vitrage-ml|vitrage-notifier|vitrage-graph)$
+    tag openstack_python
+  </rule>
 </match>
diff --git a/ansible/roles/common/templates/cron-logrotate-vitrage.conf.j2 b/ansible/roles/common/templates/cron-logrotate-vitrage.conf.j2
new file mode 100644
index 000000000..08409f080
--- /dev/null
+++ b/ansible/roles/common/templates/cron-logrotate-vitrage.conf.j2
@@ -0,0 +1,3 @@
+"/var/log/kolla/vitrage/*.log"
+{
+}
diff --git a/ansible/roles/common/templates/cron.json.j2 b/ansible/roles/common/templates/cron.json.j2
index 59f50fc2b..e8a071d2b 100644
--- a/ansible/roles/common/templates/cron.json.j2
+++ b/ansible/roles/common/templates/cron.json.j2
@@ -50,6 +50,7 @@
     ( 'tacker', enable_tacker ),
     ( 'tempest', enable_tempest ),
     ( 'trove', enable_trove ),
+    ( 'vitrage', enable_vitrage ),
     ( 'watcher', enable_watcher ),
     ( 'zun', enable_zun )
 ] %}
diff --git a/ansible/roles/haproxy/tasks/precheck.yml b/ansible/roles/haproxy/tasks/precheck.yml
index 3e5973205..c8bba7558 100644
--- a/ansible/roles/haproxy/tasks/precheck.yml
+++ b/ansible/roles/haproxy/tasks/precheck.yml
@@ -697,3 +697,14 @@
     - enable_zun | bool
     - inventory_hostname in groups['haproxy']
     - haproxy_stat.find('zun_api') == -1
+
+- name: Checking free port for Vitrage API HAProxy
+  wait_for:
+    host: "{{ kolla_internal_vip_address }}"
+    port: "{{ vitrage_api_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - enable_vitrage | bool
+    - inventory_hostname in groups['haproxy']
+    - haproxy_stat.find('vitrage_api') == -1
diff --git a/ansible/roles/haproxy/templates/haproxy.cfg.j2 b/ansible/roles/haproxy/templates/haproxy.cfg.j2
index 726fa7f74..74072bc01 100644
--- a/ansible/roles/haproxy/templates/haproxy.cfg.j2
+++ b/ansible/roles/haproxy/templates/haproxy.cfg.j2
@@ -843,6 +843,25 @@ listen skydive_server_external
 {% endif %}
 {% endif %}
 
+{% if enable_vitrage | bool %}
+listen vitrage_api
+  bind {{ kolla_internal_vip_address }}:{{ vitrage_api_port }}
+  http-request del-header X-Forwarded-Proto
+{% for host in groups['vitrage-api'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ vitrage_api_port }} check inter 2000 rise 2 fall 5
+{% endfor %}
+{% if haproxy_enable_external_vip | bool %}
+
+listen vitrage_api_external
+  bind {{ kolla_external_vip_address }}:{{ vitrage_api_port }} {{ tls_bind_info }}
+  http-request del-header X-Forwarded-Proto
+  http-request set-header X-Forwarded-Proto https if { ssl_fc }
+{% for host in groups['vitrage-api'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ vitrage_api_port }} check inter 2000 rise 2 fall 5
+{% endfor %}
+{% endif %}
+{% endif %}
+
 # (NOTE): This defaults section deletes forwardfor as recommended by:
 #         https://marc.info/?l=haproxy&m=141684110710132&w=1
 
diff --git a/ansible/roles/vitrage/defaults/main.yml b/ansible/roles/vitrage/defaults/main.yml
new file mode 100644
index 000000000..51996e37d
--- /dev/null
+++ b/ansible/roles/vitrage/defaults/main.yml
@@ -0,0 +1,129 @@
+---
+project_name: "vitrage"
+
+vitrage_services:
+  vitrage-api:
+    container_name: vitrage_api
+    group: vitrage-api
+    enabled: true
+    image: "{{ vitrage_api_image_full }}"
+    volumes:
+      - "{{ node_config_directory }}/vitrage-api/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+  vitrage-collector:
+    container_name: vitrage_collector
+    group: vitrage-collector
+    enabled: true
+    image: "{{ vitrage_collector_image_full }}"
+    volumes:
+      - "{{ node_config_directory }}/vitrage-collector/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+  vitrage-notifier:
+    container_name: vitrage_notifier
+    group: vitrage-notifier
+    enabled: true
+    image: "{{ vitrage_notifier_image_full }}"
+    volumes:
+      - "{{ node_config_directory }}/vitrage-notifier/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+  vitrage-graph:
+    container_name: vitrage_graph
+    group: vitrage-graph
+    enabled: true
+    image: "{{ vitrage_graph_image_full }}"
+    volumes:
+      - "{{ node_config_directory }}/vitrage-graph/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+  vitrage-ml:
+    container_name: vitrage_ml
+    group: vitrage-ml
+    enabled: true
+    image: "{{ vitrage_ml_image_full }}"
+    volumes:
+      - "{{ node_config_directory }}/vitrage-ml/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+
+####################
+## Database
+#####################
+vitrage_database_name: "vitrage"
+vitrage_database_user: "vitrage"
+vitrage_database_address: "{{ kolla_internal_fqdn }}:{{ database_port }}"
+
+####################
+# Docker
+####################
+
+vitrage_install_type: "{{ kolla_install_type }}"
+vitrage_tag: "{{ openstack_release }}"
+
+vitrage_graph_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ vitrage_install_type }}-vitrage-graph"
+vitrage_graph_tag: "{{ vitrage_tag }}"
+vitrage_graph_image_full: "{{ vitrage_graph_image }}:{{ vitrage_graph_tag }}"
+
+vitrage_api_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ vitrage_install_type }}-vitrage-api"
+vitrage_api_tag: "{{ vitrage_tag }}"
+vitrage_api_image_full: "{{ vitrage_api_image }}:{{ vitrage_api_tag }}"
+
+vitrage_notifier_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ vitrage_install_type }}-vitrage-notifier"
+vitrage_notifier_tag: "{{ vitrage_tag }}"
+vitrage_notifier_image_full: "{{ vitrage_notifier_image }}:{{ vitrage_notifier_tag }}"
+
+vitrage_collector_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ vitrage_install_type }}-vitrage-collector"
+vitrage_collector_tag: "{{ vitrage_tag }}"
+vitrage_collector_image_full: "{{ vitrage_collector_image }}:{{ vitrage_collector_tag }}"
+
+vitrage_ml_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ vitrage_install_type }}-vitrage-ml"
+vitrage_ml_tag: "{{ vitrage_tag }}"
+vitrage_ml_image_full: "{{ vitrage_ml_image }}:{{ vitrage_ml_tag }}"
+
+####################
+# OpenStack
+####################
+vitrage_admin_endpoint: "{{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ vitrage_api_port }}"
+vitrage_internal_endpoint: "{{ internal_protocol }}://{{ kolla_internal_fqdn }}:{{ vitrage_api_port }}"
+vitrage_public_endpoint: "{{ public_protocol }}://{{ kolla_external_fqdn }}:{{ vitrage_api_port }}"
+
+vitrage_logging_debug: "{{ openstack_logging_debug }}"
+
+vitrage_keystone_user: "vitrage"
+
+openstack_vitrage_auth: "{{ openstack_auth }}"
+
+#####################
+# Datasources
+#####################
+vitrage_notifier:
+  - name: "aodh"
+    enabled: "{{ enable_aodh | bool }}"
+  - name: "mistral"
+    enabled: "{{ enable_mistral | bool }}"
+  - name: "nova"
+    enabled: "{{ enable_nova | bool }}"
+
+vitrage_notifiers: "{{ vitrage_notifier | selectattr('enabled', 'equalto', true) | list }}"
+
+vitrage_datasource:
+  - name: "static"
+    enabled: true
+  - name: "nova.host,nova.instance,nova.zone"
+    enabled: "{{ enable_nova | bool }}"
+  - name: "aodh"
+    enabled: "{{ enable_aodh | bool }}"
+  - name: "collectd"
+    enabled: "{{ enable_collectd | bool }}"
+  - name: "cinder.volume"
+    enabled: "{{ enable_cinder | bool }}"
+  - name: "neutron.network,neutron.port"
+    enabled: "{{ enable_neutron | bool }}"
+#TODO(egonzalez) Heat cannot be used with default policy.json due stacks:global_index=rule:deny_everybody.
+# Document process to deploy vitrage+heat.
+  - name: "heat.stack"
+    enabled: "no"
+
+vitrage_datasources: "{{ vitrage_datasource | selectattr('enabled', 'equalto', true) | list }}"
diff --git a/ansible/roles/vitrage/handlers/main.yml b/ansible/roles/vitrage/handlers/main.yml
new file mode 100644
index 000000000..24b9c067b
--- /dev/null
+++ b/ansible/roles/vitrage/handlers/main.yml
@@ -0,0 +1,115 @@
+---
+- name: Restart vitrage-api container
+  vars:
+    service_name: "vitrage-api"
+    service: "{{ vitrage_services[service_name] }}"
+    config_json: "{{ vitrage_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_conf: "{{ vitrage_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    policy_json: "{{ vitrage_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_api_container: "{{ check_vitrage_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+  kolla_docker:
+    action: "recreate_or_restart_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ service.container_name }}"
+    image: "{{ service.image }}"
+    volumes: "{{ service.volumes }}"
+  when:
+    - action != "config"
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+    - config_json.changed | bool
+      or vitrage_conf.changed | bool
+      or policy_json.changed | bool
+      or vitrage_api_container.changed | bool
+
+- name: Restart vitrage-collector container
+  vars:
+    service_name: "vitrage-collector"
+    service: "{{ vitrage_services[service_name] }}"
+    config_json: "{{ vitrage_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_conf: "{{ vitrage_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    policy_json: "{{ vitrage_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_collector_container: "{{ check_vitrage_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+  kolla_docker:
+    action: "recreate_or_restart_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ service.container_name }}"
+    image: "{{ service.image }}"
+    volumes: "{{ service.volumes }}"
+  when:
+    - action != "config"
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+    - config_json.changed | bool
+      or vitrage_conf.changed | bool
+      or policy_json.changed | bool
+      or vitrage_collector_container.changed | bool
+
+- name: Restart vitrage-notifier container
+  vars:
+    service_name: "vitrage-notifier"
+    service: "{{ vitrage_services[service_name] }}"
+    config_json: "{{ vitrage_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_conf: "{{ vitrage_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    policy_json: "{{ vitrage_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_notifier_container: "{{ check_vitrage_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+  kolla_docker:
+    action: "recreate_or_restart_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ service.container_name }}"
+    image: "{{ service.image }}"
+    volumes: "{{ service.volumes }}"
+  when:
+    - action != "config"
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+    - config_json.changed | bool
+      or vitrage_conf.changed | bool
+      or policy_json.changed | bool
+      or vitrage_notifier_container.changed | bool
+
+- name: Restart vitrage-graph container
+  vars:
+    service_name: "vitrage-graph"
+    service: "{{ vitrage_services[service_name] }}"
+    config_json: "{{ vitrage_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_conf: "{{ vitrage_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    policy_json: "{{ vitrage_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_graph_container: "{{ check_vitrage_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+  kolla_docker:
+    action: "recreate_or_restart_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ service.container_name }}"
+    image: "{{ service.image }}"
+    volumes: "{{ service.volumes }}"
+  when:
+    - action != "config"
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+    - config_json.changed | bool
+      or vitrage_conf.changed | bool
+      or policy_json.changed | bool
+      or vitrage_graph_container.changed | bool
+
+- name: Restart vitrage-ml container
+  vars:
+    service_name: "vitrage-ml"
+    service: "{{ vitrage_services[service_name] }}"
+    config_json: "{{ vitrage_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_conf: "{{ vitrage_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    policy_json: "{{ vitrage_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    vitrage_ml_container: "{{ check_vitrage_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+  kolla_docker:
+    action: "recreate_or_restart_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ service.container_name }}"
+    image: "{{ service.image }}"
+    volumes: "{{ service.volumes }}"
+  when:
+    - action != "config"
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+    - config_json.changed | bool
+      or vitrage_conf.changed | bool
+      or policy_json.changed | bool
+      or vitrage_ml_container.changed | bool
diff --git a/ansible/roles/vitrage/meta/main.yml b/ansible/roles/vitrage/meta/main.yml
new file mode 100644
index 000000000..6b4fff8fe
--- /dev/null
+++ b/ansible/roles/vitrage/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - { role: common }
diff --git a/ansible/roles/vitrage/tasks/bootstrap.yml b/ansible/roles/vitrage/tasks/bootstrap.yml
new file mode 100644
index 000000000..cbe16a1ba
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/bootstrap.yml
@@ -0,0 +1,33 @@
+---
+- name: Creating vitrage database
+  kolla_toolbox:
+    module_name: mysql_db
+    module_args:
+      login_host: "{{ database_address }}"
+      login_port: "{{ database_port }}"
+      login_user: "{{ database_user }}"
+      login_password: "{{ database_password }}"
+      name: "{{ vitrage_database_name }}"
+  register: database
+  run_once: True
+  delegate_to: "{{ groups['vitrage-api'][0] }}"
+
+- name: Creating vitrage database user and setting permissions
+  kolla_toolbox:
+    module_name: mysql_user
+    module_args:
+      login_host: "{{ database_address }}"
+      login_port: "{{ database_port }}"
+      login_user: "{{ database_user }}"
+      login_password: "{{ database_password }}"
+      name: "{{ vitrage_database_user }}"
+      password: "{{ vitrage_database_password }}"
+      host: "%"
+      priv: "{{ vitrage_database_name }}.*:ALL"
+      append_privs: "yes"
+  run_once: True
+  delegate_to: "{{ groups['vitrage-api'][0] }}"
+
+- include: bootstrap_service.yml
+  when: database.changed
+
diff --git a/ansible/roles/vitrage/tasks/bootstrap_service.yml b/ansible/roles/vitrage/tasks/bootstrap_service.yml
new file mode 100644
index 000000000..ab64f2866
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/bootstrap_service.yml
@@ -0,0 +1,19 @@
+---
+- name: Running Vitrage bootstrap container
+  vars:
+    vitrage_api: "{{ vitrage_services['vitrage-api'] }}"
+  kolla_docker:
+    action: "start_container"
+    common_options: "{{ docker_common_options }}"
+    detach: False
+    environment:
+      KOLLA_BOOTSTRAP:
+      KOLLA_CONFIG_STRATEGY: "{{ config_strategy }}"
+    image: "{{ vitrage_api.image }}"
+    labels:
+      BOOTSTRAP:
+    name: "bootstrap_vitrage"
+    restart_policy: "never"
+    volumes: "{{ vitrage_api.volumes }}"
+  run_once: True
+  delegate_to: "{{ groups[vitrage_api.group][0] }}"
diff --git a/ansible/roles/vitrage/tasks/config.yml b/ansible/roles/vitrage/tasks/config.yml
new file mode 100644
index 000000000..63cc3d4d9
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/config.yml
@@ -0,0 +1,100 @@
+---
+- name: Ensuring config directories exist
+  file:
+    path: "{{ node_config_directory }}/{{ item.key }}"
+    state: "directory"
+    recurse: yes
+  when:
+    - inventory_hostname in groups[item.value.group]
+    - item.value.enabled | bool
+  with_dict: "{{ vitrage_services }}"
+
+- name: Copying over config.json files for services
+  template:
+    src: "{{ item.key }}.json.j2"
+    dest: "{{ node_config_directory }}/{{ item.key }}/config.json"
+  register: vitrage_config_jsons
+  when:
+    - inventory_hostname in groups[item.value.group]
+    - item.value.enabled | bool
+  with_dict: "{{ vitrage_services }}"
+  notify:
+    - Restart vitrage-api container
+    - Restart vitrage-collector container
+    - Restart vitrage-notifier container
+    - Restart vitrage-graph container
+    - Restart vitrage-ml container
+
+- name: Copying over vitrage.conf
+  vars:
+    service_name: "{{ item.key }}"
+  merge_configs:
+    sources:
+      - "{{ role_path }}/templates/vitrage.conf.j2"
+      - "{{ node_config_directory }}/config/global.conf"
+      - "{{ node_config_directory }}/config/messaging.conf"
+      - "{{ node_config_directory }}/config/vitrage.conf"
+      - "{{ node_config_directory }}/config/vitrage/{{ item.key }}.conf"
+      - "{{ node_config_directory }}/config/vitrage/{{ inventory_hostname }}/vitrage.conf"
+    dest: "{{ node_config_directory }}/{{ item.key }}/vitrage.conf"
+  register: vitrage_confs
+  when:
+    - inventory_hostname in groups[item.value.group]
+    - item.value.enabled | bool
+  with_dict: "{{ vitrage_services }}"
+  notify:
+    - Restart vitrage-api container
+    - Restart vitrage-collector container
+    - Restart vitrage-notifier container
+    - Restart vitrage-graph container
+    - Restart vitrage-ml container
+
+- name: Copying over wsgi-vitrage files for services
+  template:
+    src: "wsgi-vitrage.conf.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/wsgi-vitrage.conf"
+  with_items:
+    - "vitrage-api"
+  notify:
+    - Restart vitrage-api container
+
+- name: Check if policies shall be overwritten
+  local_action: stat path="{{ node_custom_config }}/vitrage/policy.json"
+  register: vitrage_policy
+
+- name: Copying over existing policy.json
+  template:
+    src: "{{ node_custom_config }}/vitrage/policy.json"
+    dest: "{{ node_config_directory }}/{{ item.key }}/policy.json"
+  register: vitrage_policy_jsons
+  when:
+    - vitrage_policy.stat.exists
+    - inventory_hostname in groups[item.value.group]
+    - item.value.enabled | bool
+  with_dict: "{{ vitrage_services }}"
+  notify:
+    - Restart vitrage-api container
+    - Restart vitrage-collector container
+    - Restart vitrage-graph container
+    - Restart vitrage-notifier container
+    - Restart vitrage-ml container
+
+- name: Check vitrage containers
+  kolla_docker:
+    action: "compare_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ item.value.container_name }}"
+    image: "{{ item.value.image }}"
+    volumes: "{{ item.value.volumes }}"
+  register: check_vitrage_containers
+  when:
+    - action != "config"
+    - inventory_hostname in groups[item.value.group]
+    - item.value.enabled | bool
+  with_dict: "{{ vitrage_services }}"
+  notify:
+    - Restart vitrage-api container
+    - Restart vitrage-collector container
+    - Restart vitrage-graph container
+    - Restart vitrage-notifier container
+    - Restart vitrage-ml container
diff --git a/ansible/roles/vitrage/tasks/deploy.yml b/ansible/roles/vitrage/tasks/deploy.yml
new file mode 100644
index 000000000..ebb653b50
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/deploy.yml
@@ -0,0 +1,16 @@
+---
+- include: register.yml
+  when: inventory_hostname in groups['vitrage-api']
+
+- include: config.yml
+  when: inventory_hostname in groups['vitrage-api'] or
+        inventory_hostname in groups['vitrage-ml'] or
+        inventory_hostname in groups['vitrage-graph'] or
+        inventory_hostname in groups['vitrage-notifier'] or
+        inventory_hostname in groups['vitrage-collector']
+
+- include: bootstrap.yml
+  when: inventory_hostname in groups['vitrage-api']
+
+- name: Flush handlers
+  meta: flush_handlers
diff --git a/ansible/roles/vitrage/tasks/main.yml b/ansible/roles/vitrage/tasks/main.yml
new file mode 100644
index 000000000..b017e8b4a
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+- include: "{{ action }}.yml"
diff --git a/ansible/roles/vitrage/tasks/precheck.yml b/ansible/roles/vitrage/tasks/precheck.yml
new file mode 100644
index 000000000..e91ab4d8d
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/precheck.yml
@@ -0,0 +1,16 @@
+---
+- name: Get container facts
+  kolla_container_facts:
+    name:
+      - vitrage_api
+  register: container_facts
+
+- name: Checking free port for vitrage API
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ vitrage_api_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - container_facts['vitrage_api'] is not defined
+    - inventory_hostname in groups['vitrage-api']
diff --git a/ansible/roles/vitrage/tasks/pull.yml b/ansible/roles/vitrage/tasks/pull.yml
new file mode 100644
index 000000000..efe5b97f9
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/pull.yml
@@ -0,0 +1,10 @@
+---
+- name: Pulling vitrage images
+  kolla_docker:
+    action: "pull_image"
+    common_options: "{{ docker_common_options }}"
+    image: "{{ item.value.image }}"
+  when:
+    - inventory_hostname in groups[item.value.group]
+    - item.value.enabled | bool
+  with_dict: "{{ vitrage_services }}"
diff --git a/ansible/roles/vitrage/tasks/reconfigure.yml b/ansible/roles/vitrage/tasks/reconfigure.yml
new file mode 100644
index 000000000..e078ef131
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/reconfigure.yml
@@ -0,0 +1,2 @@
+---
+- include: deploy.yml
diff --git a/ansible/roles/vitrage/tasks/register.yml b/ansible/roles/vitrage/tasks/register.yml
new file mode 100644
index 000000000..a80507581
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/register.yml
@@ -0,0 +1,42 @@
+---
+- name: Creating the Vitrage service and endpoint
+  kolla_toolbox:
+    module_name: "kolla_keystone_service"
+    module_args:
+      service_name: "vitrage"
+      service_type: "rca"
+      description: "Root Cause Analysis Service"
+      endpoint_region: "{{ openstack_region_name }}"
+      url: "{{ item.url }}"
+      interface: "{{ item.interface }}"
+      region_name: "{{ openstack_region_name }}"
+      auth: "{{ '{{ openstack_vitrage_auth }}' }}"
+    module_extra_vars:
+      openstack_vitrage_auth: "{{ openstack_vitrage_auth }}"
+  register: vitrage_endpoint
+  until: vitrage_endpoint|success
+  retries: 10
+  delay: 5
+  run_once: True
+  with_items:
+    - {'interface': 'admin', 'url': '{{ vitrage_admin_endpoint }}'}
+    - {'interface': 'internal', 'url': '{{ vitrage_internal_endpoint }}'}
+    - {'interface': 'public', 'url': '{{ vitrage_public_endpoint }}'}
+
+- name: Creating the Vitrage project, user, and role
+  kolla_toolbox:
+    module_name: "kolla_keystone_user"
+    module_args:
+      project: "service"
+      user: "{{ vitrage_keystone_user }}"
+      password: "{{ vitrage_keystone_password }}"
+      role: "admin"
+      region_name: "{{ openstack_region_name }}"
+      auth: "{{ '{{ openstack_vitrage_auth }}' }}"
+    module_extra_vars:
+      openstack_vitrage_auth: "{{ openstack_vitrage_auth }}"
+  register: vitrage_user
+  until: vitrage_user|success
+  retries: 10
+  delay: 5
+  run_once: True
diff --git a/ansible/roles/vitrage/tasks/upgrade.yml b/ansible/roles/vitrage/tasks/upgrade.yml
new file mode 100644
index 000000000..c38db1adf
--- /dev/null
+++ b/ansible/roles/vitrage/tasks/upgrade.yml
@@ -0,0 +1,7 @@
+---
+- include: config.yml
+
+- include: bootstrap_service.yml
+
+- name: Flush handlers
+  meta: flush_handlers
diff --git a/ansible/roles/vitrage/templates/vitrage-api.json.j2 b/ansible/roles/vitrage/templates/vitrage-api.json.j2
new file mode 100644
index 000000000..7f670f507
--- /dev/null
+++ b/ansible/roles/vitrage/templates/vitrage-api.json.j2
@@ -0,0 +1,34 @@
+{% set apache_cmd = 'apache2' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd' %}
+{% set apache_dir = 'apache2/conf-enabled' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd/conf.d' %}
+{% set apache_file = '000-default.conf' if kolla_base_distro in ['ubuntu', 'debian'] else 'vitrage-api.conf' %}
+{
+    "command": "/usr/sbin/{{ apache_cmd }} -DFOREGROUND",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/vitrage.conf",
+            "dest": "/etc/vitrage/vitrage.conf",
+            "owner": "vitrage",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/wsgi-vitrage.conf",
+            "dest": "/etc/{{ apache_dir }}/{{ apache_file }}",
+            "owner": "vitrage",
+            "perm": "0644"
+        },
+        {
+            "source": "{{ container_config_directory }}/policy.json",
+            "dest": "/etc/vitrage/policy.json",
+            "owner": "vitrage",
+            "perm": "0600",
+            "optional": true
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/vitrage",
+            "owner": "vitrage:vitrage",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/vitrage/templates/vitrage-collector.json.j2 b/ansible/roles/vitrage/templates/vitrage-collector.json.j2
new file mode 100644
index 000000000..1a6c262ea
--- /dev/null
+++ b/ansible/roles/vitrage/templates/vitrage-collector.json.j2
@@ -0,0 +1,25 @@
+{
+    "command": "vitrage-collector --config-file /etc/vitrage/vitrage.conf",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/vitrage.conf",
+            "dest": "/etc/vitrage/vitrage.conf",
+            "owner": "vitrage",
+            "perm": "0644"
+        },
+        {
+            "source": "{{ container_config_directory }}/policy.json",
+            "dest": "/etc/vitrage/policy.json",
+            "owner": "vitrage",
+            "perm": "0600",
+            "optional": true
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/vitrage",
+            "owner": "vitrage:vitrage",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/vitrage/templates/vitrage-graph.json.j2 b/ansible/roles/vitrage/templates/vitrage-graph.json.j2
new file mode 100644
index 000000000..ebbbb201b
--- /dev/null
+++ b/ansible/roles/vitrage/templates/vitrage-graph.json.j2
@@ -0,0 +1,25 @@
+{
+    "command": "vitrage-graph --config-file /etc/vitrage/vitrage.conf",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/vitrage.conf",
+            "dest": "/etc/vitrage/vitrage.conf",
+            "owner": "vitrage",
+            "perm": "0644"
+        },
+        {
+            "source": "{{ container_config_directory }}/policy.json",
+            "dest": "/etc/vitrage/policy.json",
+            "owner": "vitrage",
+            "perm": "0600",
+            "optional": true
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/vitrage",
+            "owner": "vitrage:vitrage",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/vitrage/templates/vitrage-ml.json.j2 b/ansible/roles/vitrage/templates/vitrage-ml.json.j2
new file mode 100644
index 000000000..dd8847af4
--- /dev/null
+++ b/ansible/roles/vitrage/templates/vitrage-ml.json.j2
@@ -0,0 +1,25 @@
+{
+    "command": "vitrage-ml --config-file /etc/vitrage/vitrage.conf",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/vitrage.conf",
+            "dest": "/etc/vitrage/vitrage.conf",
+            "owner": "vitrage",
+            "perm": "0644"
+        },
+        {
+            "source": "{{ container_config_directory }}/policy.json",
+            "dest": "/etc/vitrage/policy.json",
+            "owner": "vitrage",
+            "perm": "0600",
+            "optional": true
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/vitrage",
+            "owner": "vitrage:vitrage",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/vitrage/templates/vitrage-notifier.json.j2 b/ansible/roles/vitrage/templates/vitrage-notifier.json.j2
new file mode 100644
index 000000000..4ebcbcc64
--- /dev/null
+++ b/ansible/roles/vitrage/templates/vitrage-notifier.json.j2
@@ -0,0 +1,25 @@
+{
+    "command": "vitrage-notifier --config-file /etc/vitrage/vitrage.conf",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/vitrage.conf",
+            "dest": "/etc/vitrage/vitrage.conf",
+            "owner": "vitrage",
+            "perm": "0644"
+        },
+        {
+            "source": "{{ container_config_directory }}/policy.json",
+            "dest": "/etc/vitrage/policy.json",
+            "owner": "vitrage",
+            "perm": "0600",
+            "optional": true
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/vitrage",
+            "owner": "vitrage:vitrage",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/vitrage/templates/vitrage.conf.j2 b/ansible/roles/vitrage/templates/vitrage.conf.j2
new file mode 100644
index 000000000..52dad4eab
--- /dev/null
+++ b/ansible/roles/vitrage/templates/vitrage.conf.j2
@@ -0,0 +1,72 @@
+[DEFAULT]
+debug = {{ vitrage_logging_debug }}
+log_dir = /var/log/kolla/vitrage
+
+{% if service_name == 'vitrage-api' %}
+# Force vitrage-api.log or will use app.wsgi
+log_file = /var/log/kolla/vitrage/vitrage-api.log
+{% endif %}
+
+{% if vitrage_notifiers %}
+notifiers = {{ vitrage_notifiers|map(attribute='name')|join(',') }}
+{% endif %}
+
+transport_url = rabbit://{% for host in groups['rabbitmq'] %}{{ rabbitmq_user }}:{{ rabbitmq_password }}@{{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ rabbitmq_port }}{% if not loop.last %},{% endif %}{% endfor %}
+
+[api]
+host = {{ api_interface_address }}
+port = {{ vitrage_api_port }}
+workers = {{ openstack_service_workers }}
+
+[keystone_authtoken]
+auth_uri = {{ internal_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_public_port }}
+auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
+auth_type = password
+project_domain_id = default
+user_domain_id = default
+project_name = service
+username = {{ vitrage_keystone_user }}
+password = {{ vitrage_keystone_password }}
+
+memcache_security_strategy = ENCRYPT
+memcache_secret_key = {{ memcache_secret_key }}
+memcached_servers = {% for host in groups['memcached'] %}{{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ memcached_port }}{% if not loop.last %},{% endif %}{% endfor %}
+
+[database]
+connection = mysql+pymysql://{{ vitrage_database_user }}:{{ vitrage_database_password }}@{{ vitrage_database_address }}/{{ vitrage_database_name }}
+
+{% if vitrage_datasources %}
+[datasources]
+types = {{ vitrage_datasources|map(attribute='name')|join(',') }}
+{% endif %}
+
+[machine_learning]
+plugins = jaccard_correlation
+
+[service_credentials]
+auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
+region_name = {{ openstack_region_name }}
+auth_type = password
+project_domain_id = default
+user_domain_id = default
+project_name = service
+password = {{ vitrage_keystone_password }}
+username = {{ vitrage_keystone_user }}
+interface = internal
+
+[oslo_messaging_notifications]
+topics = notifications,vitrage_notifications
+driver = messagingv2
+
+[oslo_concurrency]
+lock_path = /var/lib/vitrage/tmp
+
+{% if enable_osprofiler | bool %}
+[profiler]
+enabled = true
+trace_sqlalchemy = true
+hmac_keys = {{ osprofiler_secret }}
+{% if enable_elasticsearch | bool %}
+connection_string = elasticsearch://{{ elasticsearch_address }}:{{ elasticsearch_port }}
+{% endif %}
+{% endif %}
diff --git a/ansible/roles/vitrage/templates/wsgi-vitrage.conf.j2 b/ansible/roles/vitrage/templates/wsgi-vitrage.conf.j2
new file mode 100644
index 000000000..280ce5fdb
--- /dev/null
+++ b/ansible/roles/vitrage/templates/wsgi-vitrage.conf.j2
@@ -0,0 +1,25 @@
+{% set python_path = '/usr/lib/python2.7/site-packages' if kolla_install_type == 'binary' else '/var/lib/kolla/venv/lib/python2.7/site-packages' %}
+Listen {{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}:{{ vitrage_api_port }}
+
+<VirtualHost *:{{ vitrage_api_port }}>
+
+  ## Vhost docroot
+  DocumentRoot "/var/www/cgi-bin/vitrage"
+
+  ## Directories, there should at least be a declaration for /var/www/cgi-bin/vitrage
+
+  <Directory "/var/www/cgi-bin/vitrage">
+    Options Indexes FollowSymLinks MultiViews
+    AllowOverride None
+    Require all granted
+  </Directory>
+
+  ## Logging
+  ErrorLog "/var/log/kolla/vitrage/vitrage_wsgi_error.log"
+  ServerSignature Off
+  CustomLog "/var/log/kolla/vitrage/vitrage_wsgi_access.log" combined
+  WSGIApplicationGroup %{GLOBAL}
+  WSGIDaemonProcess vitrage group=vitrage processes={{ openstack_service_workers }} threads=1 user=vitrage python-path={{ python_path }}
+  WSGIProcessGroup vitrage
+  WSGIScriptAlias / "/var/www/cgi-bin/vitrage/app.wsgi"
+</VirtualHost>
diff --git a/ansible/site.yml b/ansible/site.yml
index 47a365d27..b0ebba24f 100644
--- a/ansible/site.yml
+++ b/ansible/site.yml
@@ -716,3 +716,17 @@
     - { role: skydive,
         tags: skydive,
         when: enable_skydive | bool }
+
+- name: Apply role vitrage
+  gather_facts: false
+  hosts:
+    - vitrage-api
+    - vitrage-graph
+    - vitrage-notifier
+    - vitrage-collector
+    - vitrage-ml
+  serial: '{{ serial|default("0") }}'
+  roles:
+    - { role: vitrage,
+        tags: vitrage,
+        when: enable_vitrage | bool }
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 570c66aee..92aa3c847 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -224,6 +224,7 @@ kolla_internal_vip_address: "10.10.10.254"
 #enable_tacker: "no"
 #enable_tempest: "no"
 #enable_trove: "no"
+#enable_vitrage: "no"
 #enable_vmtp: "no"
 #enable_watcher: "no"
 #enable_zun: "no"
diff --git a/etc/kolla/passwords.yml b/etc/kolla/passwords.yml
index 1b659da29..500520e6e 100644
--- a/etc/kolla/passwords.yml
+++ b/etc/kolla/passwords.yml
@@ -160,6 +160,9 @@ tacker_keystone_password:
 zun_database_password:
 zun_keystone_password:
 
+vitrage_database_password:
+vitrage_keystone_password:
+
 memcache_secret_key:
 
 #HMAC secret key
diff --git a/releasenotes/notes/add-vitrage-6b8da2c81a68b01b.yaml b/releasenotes/notes/add-vitrage-6b8da2c81a68b01b.yaml
new file mode 100644
index 000000000..d7bab65cc
--- /dev/null
+++ b/releasenotes/notes/add-vitrage-6b8da2c81a68b01b.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add vitrage ansible role
-- 
GitLab