Collect OpenStack notifications

This change enables the LMA collector to receive OpenStack notifications
and store them into ElasticSearch. Note that we configure an additional
notification topic ('lma_notifications') instead of high-jacking the
topic used by Ceilometer ('notifications').
This adds a new checkbox option in the UI to enable/disable the
notification collection (default value: disabled).

Change-Id: Ie934c244e7cf58f58487ed903aeb94af2e2e0495
This commit is contained in:
Simon Pasquier 2015-03-02 17:03:26 +01:00
parent 23cae82af0
commit 2300b2f8cb
12 changed files with 369 additions and 44 deletions

View File

@ -0,0 +1,27 @@
define heka::input::amqp (
$ensure = present,
$config_dir,
$decoder,
$user,
$password,
$host,
$exchange,
$exchange_durability = false,
$exchange_auto_delete = false,
$queue_auto_delete = true,
$exchange_type = "topic",
$queue,
$routing_key = "*",
) {
include heka::params
file { "${config_dir}/amqp-${title}.toml":
ensure => $ensure,
content => template('heka/input/amqp.toml.erb'),
mode => '0600',
owner => $heka::params::user,
group => $heka::params::user,
require => File[$config_dir],
}
}

View File

@ -5,6 +5,6 @@ filename = "<%= @filename %>"
<% if @config.size() > 0 %>
[<%= @title %>_decoder.config]
<% @config.each do |k,v| %>
<%= k %> = '<%= v %>'
<%= k %> = <%= v.is_a?(String) ? "'#{v}'" : v %>
<% end %>
<% end %>

View File

@ -0,0 +1,12 @@
[<%= @title %>_amqp]
type = "AMQPInput"
url = "amqp://<%= @user %>:<%= @password %>@<%= @host %>/"
exchange = "<%= @exchange %>"
exchange_type = "<%= @exchange_type %>"
exchange_durability = <%= @exchange_durability %>
exchange_auto_delete = <%= @exchange_auto_delete %>
queue_auto_delete = <%= @queue_auto_delete %>
queue = "<%= @queue %>"
routing_key = "<%= @routing_key %>"
decoder = "<%= @decoder %>_decoder"
splitter = "NullSplitter"

View File

@ -24,6 +24,30 @@ local msg = {
Fields = nil
}
-- Mapping table from event_type prefixes to notification loggers
local logger_map = {
--cinder
volume = 'cinder',
-- glance
image = 'glance',
-- heat
orchestration = 'heat',
-- keystone
identity = 'keystone',
-- nova
compute = 'nova',
scheduler = 'nova',
keypair = 'nova',
-- neutron
floatingip = 'neutron',
security_group = 'neutron',
security_group_rule = 'neutron',
network = 'neutron',
port = 'neutron',
router = 'neutron',
subnet = 'neutron',
}
-- Mapping table between the attributes in the notification's payload and the
-- fields in the Heka message
local payload_fields = {
@ -78,7 +102,6 @@ local transform_functions = {
volume_id = normalize_uuid,
}
local service = read_config("service") or error("'service' configuration must be specified")
local include_full_notification = read_config("include_full_notification") or false
function process_message ()
@ -95,7 +118,7 @@ function process_message ()
end
msg.Fields = {}
msg.Logger = service
msg.Logger = logger_map[string.match(notif.event_type, '([^.]+)')]
msg.Severity = utils.label_to_severity_map[notif.priority]
msg.Timestamp = patt.Timestamp:match(notif.timestamp)
msg.Fields.publisher, msg.Hostname = string.match(notif.publisher_id, '([^.]+)%.([%w_-]+)')

View File

@ -17,7 +17,7 @@ class lma_collector::elasticsearch (
config_dir => $lma_collector::params::config_dir,
server => $server,
port => $port,
message_matcher => "Type == 'log'",
message_matcher => "Type == 'log' || Type == 'notification'",
require => Heka::Encoder::Es_json['elasticsearch'],
notify => Service[$lma_collector::params::service_name],
}

View File

@ -0,0 +1,21 @@
class lma_collector::notifications::cinder (
$topics = [],
$driver = $lma_collector::params::notification_driver,
) inherits lma_collector::params {
validate_array($topics)
include cinder::params
cinder_config {
'DEFAULT/notification_topics': value => join($topics, ','),
notify => Service[$::cinder::params::volume_service],
}
cinder_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::cinder::params::volume_service],
}
service { $::cinder::params::volume_service:
}
}

View File

@ -0,0 +1,20 @@
class lma_collector::notifications::compute (
$topics = [],
$driver = $lma_collector::params::notification_driver,
) inherits lma_collector::params {
validate_array($topics)
include nova::params
nova_config {
'DEFAULT/notification_topics': value => join($topics, ','),
notify => Service[$::nova::params::compute_service_name],
}
nova_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::nova::params::compute_service_name],
}
service { $::nova::params::compute_service_name:
}
}

View File

@ -0,0 +1,182 @@
class lma_collector::notifications::controller (
$host = $lma_collector::params::rabbitmq_host,
$user = $lma_collector::params::rabbitmq_user,
$password = $lma_collector::params::rabbitmq_password,
$driver = $lma_collector::params::notification_driver,
$topics = [],
) inherits lma_collector::params {
validate_array($topics)
$notification_topics = join($topics, ',')
# We need to pick one exchange and we settled on 'nova'. The default
# exchange ("") doesn't work because Heka would fail to create the queue in
# case it doesn't exist yet.
$exchange = 'nova'
heka::decoder::sandbox { 'notification':
config_dir => $lma_collector::params::config_dir,
filename => "${lma_collector::plugins_dir}/decoders/notification.lua" ,
config => {
include_full_notification => false
},
notify => Service[$lma_collector::params::service_name],
}
heka::input::amqp { 'openstack_info':
config_dir => $lma_collector::params::config_dir,
decoder => 'notification',
user => $user,
password => $password,
host => $host,
exchange => $exchange,
exchange_durability => false,
exchange_auto_delete => false,
queue_auto_delete => false,
exchange_type => "topic",
queue => "${lma_collector::params::lma_topic}.info",
routing_key => "${lma_collector::params::lma_topic}.info",
notify => Service[$lma_collector::params::service_name],
}
heka::input::amqp { 'openstack_error':
config_dir => $lma_collector::params::config_dir,
decoder => 'notification',
user => $user,
password => $password,
host => $host,
exchange => $exchange,
exchange_durability => false,
exchange_auto_delete => false,
queue_auto_delete => false,
exchange_type => "topic",
queue => "${lma_collector::params::lma_topic}.error",
routing_key => "${lma_collector::params::lma_topic}.error",
notify => Service[$lma_collector::params::service_name],
}
heka::input::amqp { 'openstack_warn':
config_dir => $lma_collector::params::config_dir,
decoder => 'notification',
user => $user,
password => $password,
host => $host,
exchange => $exchange,
exchange_durability => false,
exchange_auto_delete => false,
queue_auto_delete => false,
exchange_type => "topic",
queue => "${lma_collector::params::lma_topic}.warn",
routing_key => "${lma_collector::params::lma_topic}.warn",
notify => Service[$lma_collector::params::service_name],
}
# Nova
include nova::params
nova_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::nova::params::api_service_name, $::nova::params::conductor_service_name, $::nova::params::scheduler_service_name],
}
nova_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::nova::params::api_service_name, $::nova::params::conductor_service_name, $::nova::params::scheduler_service_name],
}
service { [$::nova::params::api_service_name, $::nova::params::conductor_service_name, $::nova::params::scheduler_service_name]:
}
# Cinder
include cinder::params
cinder_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::cinder::params::api_service, $::cinder::params::scheduler_service],
}
cinder_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::cinder::params::api_service, $::cinder::params::scheduler_service],
}
service { [$::cinder::params::api_service, $::cinder::params::scheduler_service]:
}
# Keystone
include keystone::params
keystone_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::keystone::params::service_name],
}
keystone_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::keystone::params::service_name],
}
service { $::keystone::params::service_name:
}
# Neutron
include neutron::params
neutron_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::neutron::params::server_service],
}
neutron_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::neutron::params::server_service],
}
service { $::neutron::params::server_service:
}
# Glance
include glance::params
# Default value is 'image.localhost' for Glance
$glance_publisher_id = "image.${::hostname}"
glance_api_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::glance::params::api_service_name],
}
glance_api_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::glance::params::api_service_name],
}
glance_api_config {
'DEFAULT/default_publisher_id': value => $glance_publisher_id,
notify => Service[$::glance::params::api_service_name],
}
glance_registry_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::glance::params::registry_service_name],
}
glance_registry_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::glance::params::registry_service_name],
}
glance_registry_config {
'DEFAULT/default_publisher_id': value => $glance_publisher_id,
notify => Service[$::glance::params::registry_service_name],
}
service { [$::glance::params::api_service_name, $::glance::params::registry_service_name]:
}
# Heat
include heat::params
heat_config {
'DEFAULT/notification_topics': value => $notification_topics,
notify => Service[$::heat::params::api_service_name, $::heat::params::engine_service_name],
}
heat_config {
'DEFAULT/notification_driver': value => $driver,
notify => Service[$::heat::params::api_service_name, $::heat::params::engine_service_name],
}
service { [$::heat::params::api_service_name, $::heat::params::engine_service_name]:
}
}

View File

@ -25,6 +25,10 @@ class lma_collector::params {
$rabbitmq_host = false
$rabbitmq_user = ''
$rabbitmq_password = ''
$rabbitmq_exchange = ''
$lma_topic = 'lma_notifications'
$openstack_topic = 'notifications'
$notification_driver = 'messaging'
$elasticsearch_server = false
$elasticsearch_port = '9200'

View File

@ -1,28 +1,19 @@
# TODO(spasquier): replace by Hiera when testing with 6.1
$fuel_settings = parseyaml(file('/etc/astute.yaml'))
$deployment_mode = $fuel_settings['deployment_mode']
if $deployment_mode == 'multinode' {
$controller_role = 'controller'
}
elsif $deployment_mode =~ /^ha/ {
$controller_role = 'primary-controller'
}
else {
fail("'${deployment_mode} is not a supported deployment mode")
}
# TODO(spasquier): fail if Neutron isn't used
# TODO: verify that we're running Neutron
include lma_collector::params
$deployment_mode = $fuel_settings['deployment_mode']
$roles = node_roles($fuel_settings['nodes'], $fuel_settings['uid'])
$is_primary_controller = member($roles, $controller_role)
$is_controller = member($roles, 'controller')
if $fuel_settings['rabbit']['user'] {
$rabbitmq_user = $fuel_settings['rabbit']['user']
}
else {
$rabbitmq_user = 'nova'
}
$roles_map = {}
$roles_map['primary-controller'] = member($roles, 'primary-controller')
$roles_map['controller'] = member($roles, 'controller')
$roles_map['compute'] = member($roles, 'compute')
$roles_map['cinder'] = member($roles, 'cinder')
$roles_map['ceph-osd'] = member($roles, 'ceph-osd')
$tags = {
deployment_id => $fuel_settings['deployment_id'],
@ -40,32 +31,69 @@ else {
$additional_tags = {}
}
class lma_common {
class { 'lma_collector':
tags => merge($tags, $additional_tags)
}
class { 'lma_collector::logs::system': }
class { 'lma_collector::logs::openstack': }
class { 'lma_collector::logs::monitor': }
$enable_notifications = $fuel_settings['lma_collector']['enable_notifications']
if $fuel_settings['ceilometer']['enabled'] {
$notification_topics = [$lma_collector::params::openstack_topic, $lma_collector::params::lma_topic]
}
else {
$notification_topics = [$lma_collector::params::lma_topic]
}
class lma_controller {
# Resources shared by all roles
class { 'lma_collector':
tags => merge($tags, $additional_tags)
}
class { 'lma_collector::logs::system': }
class { 'lma_collector::logs::openstack': }
class { 'lma_collector::logs::monitor': }
# Controller
if ($roles_map['primary-controller'] or $roles_map['controller']) {
# Logs
class { 'lma_collector::logs::mysql': }
class { 'lma_collector::logs::rabbitmq': }
class { 'lma_collector::logs::pacemaker': }
if $deployment_mode =~ /^ha/ {
class { 'lma_collector::logs::pacemaker': }
}
# Notifications
if $fuel_settings['rabbit']['user'] {
$rabbitmq_user = $fuel_settings['rabbit']['user']
}
else {
$rabbitmq_user = 'nova'
}
if $enable_notifications {
class { 'lma_collector::notifications::controller':
host => $fuel_settings['management_vip'],
user => $rabbitmq_user,
password => $fuel_settings['rabbit']['password'],
topics => $notification_topics,
}
}
}
class { 'lma_common': }
if ($is_primary_controller or $is_controller) {
class { 'lma_controller': }
# Compute
if $roles_map['compute'] and $enable_notifications {
class { 'lma_collector::notifications::compute':
topics => $notification_topics,
}
}
# Cinder
if $roles_map['cinder'] and $enable_notifications {
class { 'lma_collector::notifications::cinder':
topics => $notification_topics,
}
}
$elasticsearch_mode = $fuel_settings['lma_collector']['elasticsearch_mode']
case $elasticsearch_mode {
'remote': {

View File

@ -44,3 +44,9 @@ attributes:
regex:
source: '^[a-zA-Z\d][a-zA-Z\d_\-.]+$'
error: "Invalid address or name"
enable_notifications:
type: "checkbox"
weight: 50
value: false
label: "Collect OpenStack notifications"

View File

@ -1,8 +1,10 @@
#!/bin/bash
set -eux
shopt -s extglob
ROOT="$(dirname `readlink -f $0`)"
MODULES="${ROOT}"/deployment_scripts/puppet/modules
MODULES_DIR="${ROOT}"/deployment_scripts/puppet/modules
MODULES='@(stdlib|openstack|inifile|glance|nova|heat|neutron|keystone|cinder)'
RPM_REPO="${ROOT}"/repositories/centos/
DEB_REPO="${ROOT}"/repositories/ubuntu/
HEKA_VERSION="0.9.0"
@ -11,12 +13,12 @@ FUEL_LIB_SHA1="b977b77b7e1fca1f38e701373601e9fe3430eaee"
FUEL_LIB_TARBALL_URL="https://github.com/stackforge/fuel-library/tarball/${FUEL_LIB_SHA1}"
# Clean-up first
rm -rf ${MODULES}/{stdlib,openstack}/
rm -rf ${MODULES_DIR}/{cinder,glance,heat,inifile,keystone,neutron,nova,openstack,stdlib}
wget -qO - https://github.com/mozilla-services/heka/releases/download/v${HEKA_VERSION}/heka_${HEKA_VERSION}_amd64.deb > ${DEB_REPO}/heka_${HEKA_VERSION}_amd64.deb
wget -qO - https://github.com/mozilla-services/heka/releases/download/v${HEKA_VERSION}/heka-${HEKA_VERSION//./_}-linux-amd64.rpm > ${RPM_REPO}/heka-${HEKA_VERSION//./_}-linux-amd64.rpm
# Include dependent manifests from fuel-library
wget -qO- "${FUEL_LIB_TARBALL_URL}" | \
tar -C "${MODULES}" --strip-components=3 -zxvf - \
stackforge-fuel-library-${FUEL_LIB_SHA1:0:7}/deployment/puppet/{stdlib,keystone,openstack}
tar -C "${MODULES_DIR}" --strip-components=3 -zxvf - \
stackforge-fuel-library-${FUEL_LIB_SHA1:0:7}/deployment/puppet/{cinder,glance,heat,inifile,keystone,neutron,nova,openstack,stdlib}