Skip to content

Commit b3faf86

Browse files
committed
Merge pull request #101 from ju2wheels/monitoring
Basic Server Network Monitoring
2 parents 2aee085 + 86b7d33 commit b3faf86

4 files changed

Lines changed: 416 additions & 0 deletions

File tree

lib/softlayer/NetworkMonitor.rb

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
#--
2+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3+
#
4+
# For licensing information see the LICENSE.md file in the project root.
5+
#++
6+
7+
module SoftLayer
8+
##
9+
# This struct represents the network monitor levels of a server.
10+
# It is roughly equivalent to SoftLayer data type
11+
# SoftLayer_Network_Monitor_Version1_Query_Host_Stratum
12+
class NetworkMonitorLevels < Struct.new(:monitor_level, :response_level)
13+
def initialize(monitor_levels_data)
14+
self.monitor_level = monitor_levels_data['monitorLevel']
15+
self.response_level = monitor_levels_data['responseLevel']
16+
end
17+
end
18+
19+
##
20+
# This struct represents a network monitor query result that shows the last
21+
# state of the network monitor
22+
class NetworkMonitorQueryResult < Struct.new(:finished_at, :responded_in, :response_status)
23+
##
24+
# This constant is a mapping of network monitor query result statuses to descriptions
25+
STATUS_DESCRIPTIONS = {
26+
0 => "Down/Critical: Server is down and/or has passed the critical response threshold (extremely long ping response, abnormal behavior, etc.).",
27+
1 => "Warning - Server may be recovering from a previous down state, or may have taken too long to respond.",
28+
2 => "Up",
29+
3 => "Not used",
30+
4 => "Unknown - An unknown error has occurred. If the problem persists, contact support.",
31+
5 => "Unknown - An unknown error has occurred. If the problem persists, contact support."
32+
}
33+
34+
def initialize(query_result_data)
35+
self.finished_at = query_result_data['finishTime']
36+
self.responded_in = query_result_data['responseTime']
37+
self.response_status = query_result_data['responseStatus']
38+
end
39+
end
40+
41+
##
42+
# This struct represents a network monitor query type used for creating
43+
# new network monitors.
44+
class NetworkMonitorQueryType < Struct.new(:argument_description, :description, :id, :monitor_level, :name)
45+
def initialize(query_type_data)
46+
self.argument_description = query_type_data['arugmentDescription']
47+
self.description = query_type_data['description']
48+
self.id = query_type_data['monitorLevel']
49+
self.name = query_type_data['name']
50+
end
51+
end
52+
53+
##
54+
# This struct represents a network monitor response type used for configuring
55+
# network monitor responses when created.
56+
class NetworkMonitorResponseType < Struct.new(:action_description, :id, :level)
57+
def initialize(response_type_data)
58+
self.action_description = response_type_data['actionDescription']
59+
self.id = response_type_data['id']
60+
self.level = response_type_data['level']
61+
end
62+
end
63+
64+
##
65+
# Each SoftLayer NetworkMonitor instance provides information about network
66+
# monitors configured to check host ping or host ports of servers.
67+
#
68+
# This class roughly corresponds to the entity SofyLayer_Network_Monitor_Version1_Query_Host
69+
# in the API.
70+
#
71+
class NetworkMonitor < ModelBase
72+
include ::SoftLayer::DynamicAttribute
73+
74+
@@available_query_types = nil
75+
@@available_response_types = nil
76+
77+
##
78+
# :attr_reader: argument_value
79+
# The argument to be used for this monitor, if necessary. The lowest monitoring levels (like ping)
80+
# ignore this setting, but higher levels like HTTP custom use it.
81+
sl_attr :argument_value, 'arg1Value'
82+
83+
##
84+
# :attr_reader: ip_address
85+
# The IP address to be monitored. Must be attached to the server on this object.
86+
sl_attr :ip_address, 'ipAddress'
87+
88+
##
89+
# :attr_reader:
90+
# The status of this monitoring instance. Anything other than "ON" means that the monitor has been disabled.
91+
sl_attr :status
92+
93+
##
94+
# :attr_reader: wait_cycles
95+
# The number of 5-minute cycles to wait before the "responseAction" is taken. If set to 0, the response
96+
# action will be taken immediately.
97+
sl_attr :wait_cycles, 'waitCycles'
98+
99+
##
100+
# The most recent result for this particular monitoring instance.
101+
# :call-seq:
102+
# last_query_result(force_update=false)
103+
sl_dynamic_attr :last_query_result do |resource|
104+
resource.should_update? do
105+
#only retrieved once per instance
106+
@last_query_result == nil
107+
end
108+
109+
resource.to_update do
110+
NetworkMonitorQueryResult.new(self.service.object_mask("mask[finishTime,responseStatus,responseTime]").getLastResult)
111+
end
112+
end
113+
114+
##
115+
# The type of monitoring query that is executed when this server is monitored.
116+
# :call-seq:
117+
# query_type(force_update=false)
118+
sl_dynamic_attr :query_type do |resource|
119+
resource.should_update? do
120+
#only retrieved once per instance
121+
@query_type == nil
122+
end
123+
124+
resource.to_update do
125+
NetworkMonitorQueryType.new(self.service.getQueryType)
126+
end
127+
end
128+
129+
##
130+
# The response action taken when a monitor fails.
131+
# :call-seq:
132+
# response_type(force_update=false)
133+
sl_dynamic_attr :response_type do |resource|
134+
resource.should_update? do
135+
#only retrieved once per instance
136+
@response_type == nil
137+
end
138+
139+
resource.to_update do
140+
NetworkMonitorResponseType.new(self.service.getResponseAction)
141+
end
142+
end
143+
144+
##
145+
# Add a network monitor for a host ping or port check to a server.
146+
#
147+
def self.add_network_monitor(server, ip_address, query_type, response_type, wait_cycles = 0, argument_value = nil, options = {})
148+
softlayer_client = options[:client] || Client.default_client
149+
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
150+
raise "#{__method__} requires a server to monitor but none was given" if !server || !server.kind_of?(Server)
151+
raise "#{__method__} requires an IP address to monitor but none was given" if !ip_address || ip_address.empty?
152+
raise "#{__method__} requires a query type for the monitor but none was given" if !query_type || !query_type.kind_of?(NetworkMonitorQueryType)
153+
raise "#{__method__} requires a response type for the monitor but none was given" if !response_type || !response_type.kind_of?(NetworkMonitorResponseType)
154+
155+
if available_query_types(:client => softlayer_client, :query_level => server.network_monitor_levels.monitor_level).select{ |query| query.id == query_type.id }.empty?
156+
raise "#{__method__} requested monitor query level is not supported for this server"
157+
end
158+
159+
if available_response_types(:client => softlayer_client, :response_level => server.network_monitor_levels.response_level).select{ |response| response.id == response_type.id }.empty?
160+
raise "#{__method__} requested monitor response level is not supported for this server"
161+
end
162+
163+
network_monitor_object_filter = ObjectFilter.new()
164+
server_id_label = server.kind_of?(VirtualServer) ? 'guestId' : 'hardwareId'
165+
166+
network_monitor_object_filter.modify { |filter| filter.accept('networkMonitors.arg1Value').when_it is(argument_value.to_s) }
167+
network_monitor_object_filter.modify { |filter| filter.accept('networkMonitors.' + server_id_label).when_it is(server.id) }
168+
network_monitor_object_filter.modify { |filter| filter.accept('networkMonitors.ipAddress').when_it is(ip_address.to_s) }
169+
network_monitor_object_filter.modify { |filter| filter.accept('networkMonitors.queryTypeId').when_it is(query_type.id) }
170+
network_monitor_object_filter.modify { |filter| filter.accept('networkMonitors.responseActionId').when_it is(response_type.id) }
171+
network_monitor_object_filter.modify { |filter| filter.accept('networkMonitors.waitCycles').when_it is(wait_cycles) }
172+
173+
if server.service.object_filter(network_monitor_object_filter).getNetworkMonitors.empty?
174+
network_monitor = softlayer_client[:Network_Monitor_Version1_Query_Host].createObject({
175+
'arg1Value' => argument_value.to_s,
176+
server_id_label => server.id,
177+
'ipAddress' => ip_address.to_s,
178+
'queryTypeId' => query_type.id,
179+
'responseActionId' => response_type.id,
180+
'waitCycles' => wait_cycles
181+
})
182+
183+
NetworkMonitor.new(softlayer_client, network_monitor)
184+
end
185+
end
186+
187+
##
188+
# Add user customers to the list of users notified on monitor failure for the specified server. Accepts a list of UserCustomer
189+
# instances or user customer usernames.
190+
#
191+
def self.add_network_monitor_notification_users(server, user_customers, options = {})
192+
softlayer_client = options[:client] || Client.default_client
193+
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
194+
raise "#{__method__} requires a server to monitor but none was given" if !server || !server.kind_of?(Server)
195+
raise "#{__method__} requires a list user customers but none was given" if !user_customers || user_customers.empty?
196+
197+
user_customers_data = user_customers.map do |user_customer|
198+
raise "#{__method__} requires a user customer but none was given" if !user_customer || (!user_customer.class.method_defined?(:username) && user_customer.empty?)
199+
200+
user_customer_data = user_customer.class.method_defined?(:username) ? user_customer : UserCustomer.user_customer_with_username(user_customer, softlayer_client)
201+
202+
raise "#{__method__} user customer with username #{user_customer.inspect} not found" unless user_customer_data
203+
204+
user_customer_data
205+
end
206+
207+
current_user_customers = server.notified_network_monitor_users.map { |notified_network_monitor_user| notified_network_monitor_user['id'] }
208+
209+
user_customers_data.delete_if { |user_customer| current_user_customers.include?(user_customer['id']) }
210+
211+
unless user_customers_data.empty?
212+
notification_monitor_user_service = server.kind_of?(VirtualServer) ? :User_Customer_Notification_Virtual_Guest : :User_Customer_Notification_Hardware
213+
server_id_label = server.kind_of?(VirtualServer) ? 'guestId' : 'hardwareId'
214+
215+
user_customer_notifications = user_customers_data.map { |user_customer| { server_id_label => server.id, 'userId' => user_customer['id'] } }
216+
217+
softlayer_client[notification_monitor_user_service].createObjects(user_customer_notifications)
218+
end
219+
end
220+
221+
##
222+
# Return the list of available query types (optionally limited to a max query level)
223+
#
224+
def self.available_query_types(options = {})
225+
softlayer_client = options[:client] || Client.default_client
226+
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
227+
228+
unless @@available_query_types
229+
available_query_types_data = softlayer_client[:Network_Monitor_Version1_Query_Host_Stratum].getAllQueryTypes
230+
@@available_query_types = available_query_types_data.map{ |query_type| NetworkMonitorQueryType.new(query_type) }
231+
end
232+
233+
if options[:query_level]
234+
@@available_query_types.select { |query_type| query_type.monitor_level.to_i <= options[:query_level].to_i }
235+
else
236+
@@available_query_types
237+
end
238+
end
239+
240+
##
241+
# Return the list of available response types (optionally limited to a max response level)
242+
#
243+
def self.available_response_types(options = {})
244+
softlayer_client = options[:client] || Client.default_client
245+
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
246+
247+
unless @@available_response_types
248+
available_response_types_data = softlayer_client[:Network_Monitor_Version1_Query_Host_Stratum].getAllResponseTypes
249+
@@available_response_types = available_response_types_data.map { |response_type| NetworkMonitorResponseType.new(response_type) }
250+
end
251+
252+
if options[:response_level]
253+
@@available_response_types.select { |response_type| response_type.level.to_i <= options[:response_level].to_i }
254+
else
255+
@@available_response_types
256+
end
257+
end
258+
259+
##
260+
# Rmove user customers from the list of users notified on monitor failure for the specified server. Accepts a list of UserCustomer
261+
# instances or user customer usernames.
262+
#
263+
def self.remove_network_monitor_notification_users(server, user_customers, options = {})
264+
softlayer_client = options[:client] || Client.default_client
265+
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
266+
raise "#{__method__} requires a server to monitor but none was given" if !server || !server.kind_of?(Server)
267+
raise "#{__method__} requires a list user customers but none was given" if !user_customers || user_customers.empty?
268+
269+
user_customers_data = user_customers.map do |user_customer|
270+
raise "#{__method__} requires a user customer but none was given" if !user_customer || (!user_customer.kind_of?(UserCustomer) && user_customer.empty?)
271+
272+
user_customer_data = user_customer.kind_of?(UserCustomer) ? user_customer : UserCustomer.user_customer_with_username(user_customer, softlayer_client)
273+
274+
raise "#{__method__} user customer with username #{user_customer.inspect} not found" unless user_customer_data
275+
276+
user_customer_data
277+
end
278+
279+
current_user_customers = user_customers_data.map { |user_customer| user_customer['id'] }
280+
monitor_user_notification_object_filter = ObjectFilter.new()
281+
282+
monitor_user_notification_object_filter.modify { |filter| filter.accept('monitoringUserNotification.userId').when_it is(current_user_customers) }
283+
284+
monitor_user_notification_data = server.service.object_filter(monitor_user_notification_object_filter).object_mask("mask[id]").getMonitoringUserNotification
285+
286+
unless monitor_user_notification_data.empty?
287+
notification_monitor_user_service = server.kind_of?(VirtualServer) ? :User_Customer_Notification_Virtual_Guest : :User_Customer_Notification_Hardware
288+
289+
softlayer_client[notification_monitor_user_service].deleteObjects(monitor_user_notification_data)
290+
end
291+
end
292+
293+
##
294+
# Removes the list of network monitors from their associated servers. Accpets a list of NetworkMonitor instances or id's.
295+
#
296+
def self.remove_network_monitors(network_monitors, options = {})
297+
softlayer_client = options[:client] || Client.default_client
298+
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
299+
300+
network_monitors_data = network_monitors.map do |network_monitor|
301+
raise "#{__method__} requires a network monitor instance or id but non provided" if !network_monitor || (!network_monitor.kind_of?(NetworkMonitor) && network_monitor.empty?)
302+
303+
network_monitor.kind_of?(NetworkMonitor) ? { 'id' => network_monitor['id'] } : { 'id' => network_monitor }
304+
end
305+
306+
softlayer_client[:Network_Monitor_Version1_Query_Host].deleteObjects(network_monitors_data)
307+
end
308+
309+
##
310+
# Returns the service for interacting with this network monitor component through the network API
311+
#
312+
def service
313+
softlayer_client[:Network_Monitor_Version1_Query_Host].object_with_id(self.id)
314+
end
315+
316+
protected
317+
318+
def self.default_object_mask
319+
{
320+
"mask(SoftLayer_Network_Monitor_Version1_Query_Host)" => [
321+
'arg1Value',
322+
'guestId',
323+
'hardwareId',
324+
'hostId',
325+
'id',
326+
'ipAddress',
327+
'status',
328+
'waitCycles'
329+
]
330+
}.to_sl_object_mask
331+
end
332+
end
333+
end #SoftLayer

lib/softlayer/Server.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,65 @@ class Server < SoftLayer::ModelBase
6161
# Notes about these server (for use by the customer)
6262
sl_attr :notes
6363

64+
##
65+
# The maximum network monitor query/response levels currently supported by the server
66+
# :call-seq:
67+
# network_monitor_levels(force_update=false)
68+
sl_dynamic_attr :network_monitor_levels do |resource|
69+
resource.should_update? do
70+
@network_monitor_levels == nil
71+
end
72+
73+
resource.to_update do
74+
NetworkMonitorLevels.new(self.service.getAvailableMonitoring)
75+
end
76+
end
77+
78+
##
79+
# A lsst of configured network monitors.
80+
# :call-seq:
81+
# network_monitors(force_update=false)
82+
sl_dynamic_attr :network_monitors do |resource|
83+
resource.should_update? do
84+
@network_monitors == nil
85+
end
86+
87+
resource.to_update do
88+
network_monitors_data = self.service.object_mask(NetworkMonitor.default_object_mask).getNetworkMonitors
89+
90+
network_monitors_data.map! do |network_monitor|
91+
NetworkMonitor.new(softlayer_client, network_monitor) unless network_monitor.empty?
92+
end
93+
94+
network_monitors_data.compact
95+
end
96+
end
97+
98+
##
99+
# :attr_reader:
100+
# The list of user customers notified on monitoring failures
101+
# :call-seq:
102+
# notified_network_monitor_users(force_update=false)
103+
sl_dynamic_attr :notified_network_monitor_users do |resource|
104+
resource.should_update? do
105+
#only retrieved once per instance
106+
@notified_network_monitor_users == nil
107+
end
108+
109+
resource.to_update do
110+
notified_network_monitor_users_data = self.service.object_mask("mask[userId]").getMonitoringUserNotification
111+
112+
notified_network_monitor_users = notified_network_monitor_users_data.collect do |notified_network_monitor_user|
113+
user_customer_service = softlayer_client[:User_Customer].object_with_id(notified_network_monitor_user['userId'])
114+
user_customer_data = user_customer_service.object_mask(UserCustomer.default_object_mask).getObject
115+
116+
UserCustomer.new(softlayer_client, user_customer_data) unless user_customer_data.empty?
117+
end
118+
119+
notified_network_monitor_users.compact
120+
end
121+
end
122+
64123
##
65124
# Retrieve the primary network component
66125
# :call-seq:

0 commit comments

Comments
 (0)