From ebc5231f146c159d2377e6967adbc5c48668aeb5 Mon Sep 17 00:00:00 2001 From: Pal David Gergely Date: Thu, 24 Jul 2014 17:36:04 +0200 Subject: [PATCH 1/5] Added screen resource with provider, supporting the usual actions plus the ability to looking up the resources for the screenitems --- providers/screen.rb | 205 ++++++++++++++++++++++++++++++++++++++++++++ resources/screen.rb | 62 ++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 providers/screen.rb create mode 100644 resources/screen.rb diff --git a/providers/screen.rb b/providers/screen.rb new file mode 100644 index 0000000..c9ae640 --- /dev/null +++ b/providers/screen.rb @@ -0,0 +1,205 @@ +action :create_or_update do + Chef::Zabbix.with_connection(new_resource.server_connection) do |connection| + get_screen_request = { + :method => 'screen.get', + :params => { + :filter => { + :name => new_resource.name + } + } + } + screens = connection.query(get_screen_request) + + if screens.size == 0 + Chef::Log.info "Screen does not exists. Proceeding to create this screen on the Zabbix server: '#{new_resource.name}'" + run_action :create + new_resource.updated_by_last_action(true) + else + Chef::Log.info "Going to update this screen (if needed): '#{new_resource.name}'" + run_action :update + end + end +end + +action :create do + Chef::Zabbix.with_connection(new_resource.server_connection) do |connection| + + looked_up_screen_items = add_resource_ids_for_screen_items(new_resource.screen_items, connection) + + request = { + :method => 'screen.create', + :params => { + :name => new_resource.name, + :hsize => new_resource.hsize, + :vsize => new_resource.vsize, + :screenitems => looked_up_screen_items + } + } + Chef::Log.info "Creating new screen: '#{new_resource.name}'" + connection.query(request) + end + new_resource.updated_by_last_action(true) +end + +action :update do + Chef::Zabbix.with_connection(new_resource.server_connection) do |connection| + + get_screen_request = { + :method => 'screen.get', + :params => { + :filter => { + :name => new_resource.name + }, + :output => :extend, + :selectScreenItems => :extend + } + } + screen = connection.query(get_screen_request) + if screen.nil? || screen.empty? + Chef::Application.fatal! "Could not find screen for update: '#{new_resource.name}'" + else + screen = screen.first + end + + looked_up_screen_items = add_resource_ids_for_screen_items(new_resource.screen_items, connection) + + need_to_update = false + + %w(name vsize hsize).each do |attr_name| + need_to_update = true if screen[attr_name] != new_resource.send(attr_name).to_s + end + + # We sort both of the arrays to make sure we can compare their elements + # one by one + screen['screenitems'].sort_by! { |hsh| hsh['resourceid'] } + looked_up_screen_items.sort_by! { |hsh| hsh[:resourceid] } + + looked_up_screen_items.each_with_index do |screen_item, index| + screen_item.each do |key, value| + need_to_update = true if value.to_s != screen['screenitems'][index][key.to_s] + end + end + + if need_to_update + screen_update_request = { + :method => 'screen.update', + :params => { + :screenid => screen['screenid'], + :name => new_resource.name, + :hsize => new_resource.hsize, + :vsize => new_resource.vsize, + :screenitems => looked_up_screen_items + } + } + Chef::Log.info "Updating screen '#{new_resource.name}'" + connection.query(screen_update_request) + new_resource.updated_by_last_action(true) + else + Chef::Log.info "The attributes of screen '#{new_resource.name}' are already up-to-date, doing nothing" + end + + end +end + +action :delete do + Chef::Zabbix.with_connection(new_resource.server_connection) do |connection| + + get_screen_request = { + :method => 'screen.get', + :params => { + :filter => { + :name => new_resource.name + }, + :output => :shorten + } + } + screen = connection.query(get_screen_request) + if screen.nil? || screen.empty? + Chef::Application.fatal! "Could not find screen for deletion: '#{new_resource.name}'" + else + screen = screen.first + end + + screen_delete_request = { + :method => 'screen.delete', + :params => [ + screen['screenid'] + ] + } + Chef::Log.info "Deleting screen '#{new_resource.name}'" + result = connection.query(screen_delete_request) + Application.fatal! "Error deleting screen '#{new_resource.name}', see Chef errors" if result.nil? || result.empty? || result['screenids'].nil? || result['screenids'].empty? || !result['screenids'].include?(screen['screenid']) + new_resource.updated_by_last_action(true) + end +end + +def load_current_resource + run_context.include_recipe 'zabbix::_providers_common' + require 'zabbixapi' +end + +def add_resource_ids_for_screen_items(screen_items, connection) + returned_screen_items = [] + screen_items.each do |current_screen_item| + if current_screen_item[:resourceid].kind_of? Fixnum + # We make it to a String for possible later comparison/sorting, because the API + # can only return String values anyway + current_screen_item[:resourceid] = current_screen_item[:resourceid].to_s + returned_screen_items.push current_screen_item + else + if current_screen_item[:resourcetype] == 0 + Chef::Application.fatal! "When 'resourcetype' is 0 and 'resourceid' is a Hash, then you must set 'name' in that Hash" if current_screen_item[:resourceid][:name].nil? + Chef::Log.debug "Checking graph id for: host: '#{current_screen_item[:resourceid][:host]}', name: '#{current_screen_item[:resourceid][:name]}'" + current_screen_item[:resourceid] = get_graph_id_from_graph_name(current_screen_item[:resourceid][:host], current_screen_item[:resourceid][:name], connection) + elsif current_screen_item[:resourcetype] == 1 + Chef::Application.fatal! "When 'resourcetype' is 1 and 'resourceid' is a Hash, then you must set 'key_' in that Hash" if current_screen_item[:resourceid][:key_].nil? + Chef::Log.debug "Checking item id for: host: '#{current_screen_item[:resourceid][:host]}', key: '#{current_screen_item[:resourceid][:key_]}'" + current_screen_item[:resourceid] = get_item_id_from_item_key(current_screen_item[:resourceid][:host], current_screen_item[:resourceid][:key_], connection) + end + returned_screen_items.push current_screen_item + end + end + returned_screen_items +end + +def get_graph_id_from_graph_name(host_name, name, connection) + return_id = nil + get_graph_id_request = { + :method => 'host.get', + :params => { + :selectGraphs => ['graphid', 'name'], + :output => :shorten, + :filter => { + :host => host_name + } + } + } + graph_id_result = connection.query(get_graph_id_request) + Chef::Application.fatal! "Could not find graphs at all for host: '#{host_name}'" if graph_id_result.nil? || graph_id_result.empty? + graph_id_result.first['graphs'].each do |graph| + return_id = graph['graphid'] if graph['name'] == name + end + Chef::Application.fatal! "Could not find graph id for host: '#{host_name}', name: '#{name}'" if return_id.nil? + return_id +end + +def get_item_id_from_item_key(host_name, key_, connection) + return_id = nil + get_item_id_request = { + :method => 'host.get', + :params => { + :selectItems => ['itemid', 'key_'], + :output => :shorten, + :filter => { + :host => host_name + } + } + } + item_id_result = connection.query(get_item_id_request) + Chef::Application.fatal! "Could not find items at all for host: '#{host_name}'" if item_id_result.nil? || item_id_result.empty? + item_id_result.first['items'].each do |item| + return_id = item['itemid'] if item['key_'] == key_ + end + Chef::Application.fatal! "Could not find item id for host: '#{host_name}', key_: '#{key_}'" if return_id.nil? + return_id +end diff --git a/resources/screen.rb b/resources/screen.rb new file mode 100644 index 0000000..c8ae057 --- /dev/null +++ b/resources/screen.rb @@ -0,0 +1,62 @@ +actions :create_or_update, :create, :update, :delete +default_action :create_or_update + +attribute :name, :kind_of => String, :required => true + +attribute :hsize, :kind_of => Integer, :default => 1 +attribute :vsize, :kind_of => Integer, :default => 1 + +# This accepts an Array of Zabbix ScreenItem objects supplied as Ruby +# Hashes for the attributes of attached ScreenItem objects +# +# The `resourceid` is a special attribute, which can either be: +# - Fixnum: in that case that will be used in the Zabbix API as-is +# - Hash: containing the `:host` symbol as a key to indicate which +# host machine's resources will we use, and also containing either of these: +# - name: only valid when the `resourcetype` is 0, it will be used to get +# the graph of the given host as a normal graph resource +# - key_: only valid when the `resourcetype` is 1, it will identify the item +# of the given host to use as a simple graph resource +# Please note: only `resourcetype` 0 and 1 lookups are supported currently +# +# Example: +# [{ +# resourcetype: 1, +# colspan: 1, +# rowspan: 1, +# elements: 25, +# height: 200, +# resourceid: {host: 'Host Name', key_: 'system.cpu.load[percpu,avg1]'}, +# width: 320, +# x: 1, +# y: 1 +# }, +# { +# resourcetype: 0, +# colspan: 1, +# rowspan: 1, +# elements: 25, +# height: 200, +# resourceid: {host: 'Host Name', name: 'CPU load'}, +# width: 320, +# x: 1, +# y: 1 +# }, +# { +# resourcetype: 0 +# colspan: 1, +# rowspan: 1, +# elements: 25, +# height: 200, +# resourceid: 581, +# width: 320, +# x: 1, +# y: 1 +# }] +attribute :screen_items, :kind_of => Array, :default => [] + +# This is a Ruby Hash object with 3 required parameters used for connecting to +# the Zabbix server's API. An example could be this: +# { url: "https://zabbix.server.address.com/api_jsonrpc.php", user: 'Admin', +# password: 'zabbix' } +attribute :server_connection, :kind_of => Hash, :default => {} From bf383fbb757419269729b1f797ef4bfecb03e4e2 Mon Sep 17 00:00:00 2001 From: Pal David Gergely Date: Tue, 29 Jul 2014 12:27:17 +0200 Subject: [PATCH 2/5] Added `resourcetype` = 3 lookup support for screen resource --- providers/screen.rb | 4 ++-- resources/screen.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/providers/screen.rb b/providers/screen.rb index c9ae640..7896edb 100644 --- a/providers/screen.rb +++ b/providers/screen.rb @@ -151,8 +151,8 @@ def add_resource_ids_for_screen_items(screen_items, connection) Chef::Application.fatal! "When 'resourcetype' is 0 and 'resourceid' is a Hash, then you must set 'name' in that Hash" if current_screen_item[:resourceid][:name].nil? Chef::Log.debug "Checking graph id for: host: '#{current_screen_item[:resourceid][:host]}', name: '#{current_screen_item[:resourceid][:name]}'" current_screen_item[:resourceid] = get_graph_id_from_graph_name(current_screen_item[:resourceid][:host], current_screen_item[:resourceid][:name], connection) - elsif current_screen_item[:resourcetype] == 1 - Chef::Application.fatal! "When 'resourcetype' is 1 and 'resourceid' is a Hash, then you must set 'key_' in that Hash" if current_screen_item[:resourceid][:key_].nil? + elsif current_screen_item[:resourcetype] == 1 || current_screen_item[:resourcetype] == 3 + Chef::Application.fatal! "When 'resourcetype' is 1 or 3 and 'resourceid' is a Hash, then you must set 'key_' in that Hash" if current_screen_item[:resourceid][:key_].nil? Chef::Log.debug "Checking item id for: host: '#{current_screen_item[:resourceid][:host]}', key: '#{current_screen_item[:resourceid][:key_]}'" current_screen_item[:resourceid] = get_item_id_from_item_key(current_screen_item[:resourceid][:host], current_screen_item[:resourceid][:key_], connection) end diff --git a/resources/screen.rb b/resources/screen.rb index c8ae057..168849c 100644 --- a/resources/screen.rb +++ b/resources/screen.rb @@ -17,7 +17,7 @@ # the graph of the given host as a normal graph resource # - key_: only valid when the `resourcetype` is 1, it will identify the item # of the given host to use as a simple graph resource -# Please note: only `resourcetype` 0 and 1 lookups are supported currently +# Please note: only `resourcetype` 0, 1 and 3 lookups are supported currently # # Example: # [{ From b9ca3513c0ef4bb25180ab612bc67bbc1dbcfb5d Mon Sep 17 00:00:00 2001 From: Pal David Gergely Date: Tue, 5 Aug 2014 17:59:53 +0200 Subject: [PATCH 3/5] Modified screen update check to make sure it will not try to [] a Nil --- providers/screen.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/screen.rb b/providers/screen.rb index 7896edb..fd9777b 100644 --- a/providers/screen.rb +++ b/providers/screen.rb @@ -76,7 +76,7 @@ looked_up_screen_items.each_with_index do |screen_item, index| screen_item.each do |key, value| - need_to_update = true if value.to_s != screen['screenitems'][index][key.to_s] + need_to_update = true if screen['screenitems'][index].nil? || value.to_s != screen['screenitems'][index][key.to_s] end end From c0537395d26b8564113112bbb213d80bf174035e Mon Sep 17 00:00:00 2001 From: Pal David Gergely Date: Mon, 23 Feb 2015 13:56:29 +0100 Subject: [PATCH 4/5] Also added a test for the screen resource --- .kitchen.yml | 9 +++ Berksfile | 5 ++ test/kitchen/Kitchenfile | 1 + .../kitchen/cookbooks/zabbix_test/metadata.rb | 3 + .../zabbix_test/recipes/screen_resource.rb | 76 +++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb diff --git a/.kitchen.yml b/.kitchen.yml index 25d1b6d..6cb2471 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -19,3 +19,12 @@ suites: run_list: - "recipe[zabbix::default]" attributes: +- name: screen_resource + run_list: + - recipe[apt] + - recipe[zabbix_test::screen_resource] + attributes: + excludes: + - ubuntu-10.04 + - centos-5.9 + - centos-6.4 diff --git a/Berksfile b/Berksfile index 44bc4fc..6014543 100644 --- a/Berksfile +++ b/Berksfile @@ -1,3 +1,8 @@ source 'https://api.berkshelf.com' metadata + +cookbook 'apt' +cookbook 'mysql', '~> 5.5.4' + +cookbook 'zabbix_test', :path => './test/kitchen/cookbooks/zabbix_test' diff --git a/test/kitchen/Kitchenfile b/test/kitchen/Kitchenfile index 9fd07f4..49a10c1 100644 --- a/test/kitchen/Kitchenfile +++ b/test/kitchen/Kitchenfile @@ -1,6 +1,7 @@ cookbook "zabbix" do configuration "default" configuration "server_source" + configuration "screen_resource" exclude :platform => 'centos' exclude :platform => 'debian' exclude :platform => 'redhat' diff --git a/test/kitchen/cookbooks/zabbix_test/metadata.rb b/test/kitchen/cookbooks/zabbix_test/metadata.rb index df72ca4..a5cfd3b 100644 --- a/test/kitchen/cookbooks/zabbix_test/metadata.rb +++ b/test/kitchen/cookbooks/zabbix_test/metadata.rb @@ -1,5 +1,8 @@ +name 'zabbix_test' maintainer 'Efactures' maintainer_email 'nacer.laradji@gmail.com' license 'Apache 2.0' description 'This cookbook is used with test-kitchen to test the parent, zabbix cookbook.' version '1.0.0' + +depends 'zabbix' diff --git a/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb b/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb new file mode 100644 index 0000000..24bf6b7 --- /dev/null +++ b/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb @@ -0,0 +1,76 @@ +# Author:: Pal David Gergely () +# Cookbook Name:: zabbix-test +# Recipe:: screen_resource +# +# Copyright 2015, Pal David Gergely +# +# Apache 2.0 + +# First a working Zabbix server is needed to have an API to test the resource +# against it, so intalling it with a database +node.normal['zabbix']['server']['install'] = true +node.normal['zabbix']['server']['version'] = '2.2.8' +node.normal['zabbix']['database']['dbpassword'] = 'foobar' +include_recipe 'mysql::server' +# Needed for the mysql gem install +package 'libmysqlclient-dev' +include_recipe 'zabbix::database' +include_recipe 'zabbix::server' +include_recipe 'zabbix::web' + +# Now trigger an immediate restart on Apache to get the Zabbix API working +# before using it, TODO: not very nice, but needed +service 'apache2' do + action :reload +end + +# Also add the VM itself to Zabbix hosts, since monitored items are needed for +# the screen items to be able to test them +zabbix_host 'localhost' do + parameters({ + groupNames: ['Linux servers'], + templates: ['Template OS Linux'], + interfaces: [Chef::Zabbix::API::HostInterface.new(type: Chef::Zabbix::API::HostInterfaceType.new(1), main: 1, useip: 1, ip: '127.0.0.1', port: 10050)] + }) + server_connection({ + url: 'http://127.0.0.1/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + }) +end + +# Then create a simple test screen to validate that the screen resource is +# working +zabbix_screen 'test-screen' do + hsize 2 + vsize 2 + screen_items [ + { + resourcetype: 1, + colspan: 1, + rowspan: 1, + elements: 25, + height: 200, + resourceid: {host: 'localhost', key_: 'system.cpu.load[percpu,avg1]'}, + width: 320, + x: 0, + y: 0 + }, + { + resourcetype: 0, + colspan: 0, + rowspan: 0, + elements: 25, + height: 200, + resourceid: {host: 'localhost', name: 'CPU load'}, + width: 320, + x: 1, + y: 1 + } + ] + server_connection({ + url: 'http://127.0.0.1/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + }) +end From 317d547bde7ba46ed45fbd2f9ea2b92250dea43a Mon Sep 17 00:00:00 2001 From: Pal David Gergely Date: Thu, 26 Feb 2015 13:50:29 +0100 Subject: [PATCH 5/5] screen_resource provider and screen_resource test cookbook: corrected RuboCop offenses --- providers/screen.rb | 9 +- .../zabbix_test/recipes/screen_resource.rb | 82 +++++++++---------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/providers/screen.rb b/providers/screen.rb index fd9777b..267b79a 100644 --- a/providers/screen.rb +++ b/providers/screen.rb @@ -141,20 +141,23 @@ def load_current_resource def add_resource_ids_for_screen_items(screen_items, connection) returned_screen_items = [] screen_items.each do |current_screen_item| - if current_screen_item[:resourceid].kind_of? Fixnum + if current_screen_item[:resourceid].is_a? Fixnum # We make it to a String for possible later comparison/sorting, because the API # can only return String values anyway current_screen_item[:resourceid] = current_screen_item[:resourceid].to_s returned_screen_items.push current_screen_item else - if current_screen_item[:resourcetype] == 0 + case current_screen_item[:resourcetype] + when 0 Chef::Application.fatal! "When 'resourcetype' is 0 and 'resourceid' is a Hash, then you must set 'name' in that Hash" if current_screen_item[:resourceid][:name].nil? Chef::Log.debug "Checking graph id for: host: '#{current_screen_item[:resourceid][:host]}', name: '#{current_screen_item[:resourceid][:name]}'" current_screen_item[:resourceid] = get_graph_id_from_graph_name(current_screen_item[:resourceid][:host], current_screen_item[:resourceid][:name], connection) - elsif current_screen_item[:resourcetype] == 1 || current_screen_item[:resourcetype] == 3 + when 1, 3 Chef::Application.fatal! "When 'resourcetype' is 1 or 3 and 'resourceid' is a Hash, then you must set 'key_' in that Hash" if current_screen_item[:resourceid][:key_].nil? Chef::Log.debug "Checking item id for: host: '#{current_screen_item[:resourceid][:host]}', key: '#{current_screen_item[:resourceid][:key_]}'" current_screen_item[:resourceid] = get_item_id_from_item_key(current_screen_item[:resourceid][:host], current_screen_item[:resourceid][:key_], connection) + else + Chef::Application.fatal! "You either have to supply a correct Numeric 'resourceid' paramater or if you use a lookup Hash instead, then the 'resourcetype' must be 0, 1 or 3!" end returned_screen_items.push current_screen_item end diff --git a/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb b/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb index 24bf6b7..88f5d27 100644 --- a/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb +++ b/test/kitchen/cookbooks/zabbix_test/recipes/screen_resource.rb @@ -27,50 +27,50 @@ # Also add the VM itself to Zabbix hosts, since monitored items are needed for # the screen items to be able to test them zabbix_host 'localhost' do - parameters({ - groupNames: ['Linux servers'], - templates: ['Template OS Linux'], - interfaces: [Chef::Zabbix::API::HostInterface.new(type: Chef::Zabbix::API::HostInterfaceType.new(1), main: 1, useip: 1, ip: '127.0.0.1', port: 10050)] - }) - server_connection({ - url: 'http://127.0.0.1/api_jsonrpc.php', - user: 'Admin', - password: 'zabbix' - }) + parameters( + groupNames: ['Linux servers'], + templates: ['Template OS Linux'], + interfaces: [Chef::Zabbix::API::HostInterface.new(type: Chef::Zabbix::API::HostInterfaceType.new(1), main: 1, useip: 1, ip: '127.0.0.1', port: 10_050)] + ) + server_connection( + url: 'http://127.0.0.1/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + ) end # Then create a simple test screen to validate that the screen resource is # working zabbix_screen 'test-screen' do - hsize 2 - vsize 2 - screen_items [ - { - resourcetype: 1, - colspan: 1, - rowspan: 1, - elements: 25, - height: 200, - resourceid: {host: 'localhost', key_: 'system.cpu.load[percpu,avg1]'}, - width: 320, - x: 0, - y: 0 - }, - { - resourcetype: 0, - colspan: 0, - rowspan: 0, - elements: 25, - height: 200, - resourceid: {host: 'localhost', name: 'CPU load'}, - width: 320, - x: 1, - y: 1 - } - ] - server_connection({ - url: 'http://127.0.0.1/api_jsonrpc.php', - user: 'Admin', - password: 'zabbix' - }) + hsize 2 + vsize 2 + screen_items [ + { + resourcetype: 1, + colspan: 1, + rowspan: 1, + elements: 25, + height: 200, + resourceid: { host: 'localhost', key_: 'system.cpu.load[percpu,avg1]' }, + width: 320, + x: 0, + y: 0 + }, + { + resourcetype: 0, + colspan: 0, + rowspan: 0, + elements: 25, + height: 200, + resourceid: { host: 'localhost', name: 'CPU load' }, + width: 320, + x: 1, + y: 1 + } + ] + server_connection( + url: 'http://127.0.0.1/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + ) end