Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-add service level objectives support #224

Merged
merged 3 commits into from
Feb 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,6 @@ Style/YodaCondition:
Style/ZeroLengthPredicate:
Exclude:
- 'lib/capistrano/datadog.rb'

Style/SymbolArray:
EnforcedStyle: brackets # Since ruby1.9 is supported
10 changes: 10 additions & 0 deletions lib/dogapi/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,14 @@ def Dogapi.validate_tags(tags)
raise ArgumentError, "Each tag needs to be a string. Current value: #{tag}" unless tag.is_a? String
end
end

# Very simplified hash with indifferent access - access to string or symbol
# keys via symbols. E.g.:
# my_hash = { 'foo' => 1 }
# Dogapi.symbolized_access(my_hash)
# my_hash[:foo] # => 1
def Dogapi.symbolized_access(hash)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind this one ?
We already have a few endpoints that use a hash for options, but aren't using this symbolized access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking across the other usages, all of the other options are merged into the body without modification. In a few cases here we construct a deeper object in the body based on the flat options passed in.

hash.default_proc = proc { |h, k| h.key?(k.to_s) ? h[k.to_s] : nil }
hash
end
end
43 changes: 43 additions & 0 deletions lib/dogapi/facade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def initialize(api_key, application_key=nil, host=nil, device=nil, silent=true,
@usage_svc = Dogapi::V1::UsageService.new(@api_key, @application_key, silent, timeout, @datadog_host)
@azure_integration_svc = Dogapi::V1::AzureIntegrationService.new(@api_key, @application_key, silent, timeout, @datadog_host)
@gcp_integration_svc = Dogapi::V1::GcpIntegrationService.new(@api_key, @application_key, silent, timeout, @datadog_host)
@service_level_objective_svc = Dogapi::V1::ServiceLevelObjectiveService.new(@api_key, @application_key, silent,
timeout, @datadog_host)

# Support for Dashboard List API v2.
@v2 = Dogapi::ClientV2.new(@api_key, @application_key, true, true, @datadog_host)

Expand Down Expand Up @@ -673,6 +676,46 @@ def unmute_host(hostname)
@monitor_svc.unmute_host(hostname)
end

#
# SERVICE LEVEL OBJECTIVES
#

def create_service_level_objective(type, slo_name, thresholds, options = {})
@service_level_objective_svc.create_service_level_objective(type, slo_name, thresholds, options)
end

def update_service_level_objective(slo_id, type, options = {})
@service_level_objective_svc.update_service_level_objective(slo_id, type, options)
end

def get_service_level_objective(slo_id)
@service_level_objective_svc.get_service_level_objective(slo_id)
end

def get_service_level_objective_history(slo_id, from_ts, to_ts)
@service_level_objective_svc.get_service_level_objective_history(slo_id, from_ts, to_ts)
end

def search_service_level_objective(slo_ids = nil, query = nil, offset = nil, limit = nil)
@service_level_objective_svc.search_service_level_objective(slo_ids, query, offset, limit)
end

def can_delete_service_level_objective(slo_ids)
@service_level_objective_svc.can_delete_service_level_objective(slo_ids)
end

def delete_service_level_objective(slo_id)
@service_level_objective_svc.delete_service_level_objective(slo_id)
end

def delete_many_service_level_objective(slo_ids)
@service_level_objective_svc.delete_many_service_level_objective(slo_ids)
end

def delete_timeframes_service_level_objective(ops)
@service_level_objective_svc.delete_timeframes_service_level_objective(ops)
end

#
# SERVICE CHECKS
#
Expand Down
1 change: 1 addition & 0 deletions lib/dogapi/v1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require 'dogapi/v1/screenboard'
require 'dogapi/v1/search'
require 'dogapi/v1/service_check'
require 'dogapi/v1/service_level_objective'
require 'dogapi/v1/snapshot'
require 'dogapi/v1/synthetics'
require 'dogapi/v1/tag'
Expand Down
113 changes: 113 additions & 0 deletions lib/dogapi/v1/service_level_objective.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
require 'dogapi'

module Dogapi
class V1 # for namespacing

# Implements Service Level Objectives endpoints
class ServiceLevelObjectiveService < Dogapi::APIService

API_VERSION = 'v1'

def create_service_level_objective(type, slo_name, thresholds, options = {})
body = {
type: type,
name: slo_name,
thresholds: thresholds
}

symbolized_options = Dogapi.symbolized_access(options)
if type.to_s == 'metric'
body[:query] = {
numerator: symbolized_options[:numerator],
denominator: symbolized_options[:denominator]
}
else
body[:monitor_search] = symbolized_options[:monitor_search] if symbolized_options[:monitor_search]
body[:monitor_ids] = symbolized_options[:monitor_ids] if symbolized_options[:monitor_ids]
body[:groups] = symbolized_options[:groups] if symbolized_options[:groups]
end
body[:tags] = symbolized_options[:tags] if symbolized_options[:tags]
body[:description] = symbolized_options[:description] if symbolized_options[:description]

request(Net::HTTP::Post, "/api/#{API_VERSION}/slo", nil, body, true)
end

def update_service_level_objective(slo_id, type, options = {})
body = {
type: type
}

symbolized_options = Dogapi.symbolized_access(options)
if type == 'metric'
if symbolized_options[:numerator] && symbolized_options[:denominator]
body[:query] = {
numerator: symbolized_options[:numerator],
denominator: symbolized_options[:denominator]
}
end
else
body[:monitor_search] = symbolized_options[:monitor_search] if symbolized_options[:monitor_search]
body[:monitor_ids] = symbolized_options[:monitor_ids] if symbolized_options[:monitor_ids]
body[:groups] = symbolized_options[:groups] if symbolized_options[:groups]
end
[:name, :thresholds, :tags, :description].each do |a|
body[a] = symbolized_options[a] if symbolized_options[a]
end

request(Net::HTTP::Put, "/api/#{API_VERSION}/slo/#{slo_id}", nil, body, true)
end

def get_service_level_objective(slo_id)
request(Net::HTTP::Get, "/api/#{API_VERSION}/slo/#{slo_id}", nil, nil, false)
end

def search_service_level_objective(slo_ids, query, offset, limit)
params = {}
params[:offset] = offset unless offset.nil?
params[:limit] = limit unless limit.nil?
if !slo_ids.nil?
params[:ids] = slo_ids.join(',')
else
params[:query] = query
end

request(Net::HTTP::Get, "/api/#{API_VERSION}/slo/", params, nil, false)
end

def delete_service_level_objective(slo_id)
request(Net::HTTP::Delete, "/api/#{API_VERSION}/slo/#{slo_id}", nil, nil, false)
end

def delete_many_service_level_objective(slo_ids)
body = {
ids: slo_ids
}
request(Net::HTTP::Delete, "/api/#{API_VERSION}/slo/", nil, body, true)
end

def delete_timeframes_service_level_objective(ops)
# ops is a hash of slo_id: [<timeframe>] to delete
request(Net::HTTP::Post, "/api/#{API_VERSION}/slo/bulk_delete", nil, ops, true)
end

def get_service_level_objective_history(slo_id, from_ts, to_ts)
params = {
from_ts: from_ts,
to_ts: to_ts
}
request(Net::HTTP::Get, "/api/#{API_VERSION}/slo/#{slo_id}/history", params, nil, false)
end

def can_delete_service_level_objective(slo_ids)
params = {}
params[:ids] = if slo_ids.is_a? Array
slo_ids.join(',')
else
slo_ids
end
request(Net::HTTP::Get, "/api/#{API_VERSION}/slo/can_delete", params, nil, false)
end

end
end
end
73 changes: 73 additions & 0 deletions spec/integration/service_level_objective_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require_relative '../spec_helper'

describe Dogapi::Client do
SLO_ID = '42424242424242424242424242424242'.freeze
SLO_TYPE = 'metric'.freeze
SLO_NAME = 'test slo'.freeze
SLO_DESCRIPTION = 'test slo description'.freeze
SLO_QUERY_NUMERATOR = 'sum:test.metric.metric{type:good}.as_count()'.freeze
SLO_QUERY_DENOMINATOR = 'sum:test.metric.metric{*}.as_count()'.freeze
SLO_TAGS = ['type:test'].freeze
SLO_THRESHOLDS = [{ timeframe: '7d', target: 90 }, { timeframe: '30d', target: 95 }].freeze

describe '#create_service_level_objective' do
it_behaves_like 'an api method',
:create_service_level_objective, [SLO_TYPE, SLO_NAME, SLO_THRESHOLDS, {
description: SLO_DESCRIPTION,
tags: SLO_TAGS,
numerator: SLO_QUERY_NUMERATOR,
denominator: SLO_QUERY_DENOMINATOR
}],
:post, '/slo', 'type' => SLO_TYPE, 'name' => SLO_NAME, 'thresholds' => SLO_THRESHOLDS,
'query' => { numerator: SLO_QUERY_NUMERATOR, denominator: SLO_QUERY_DENOMINATOR },
'tags' => SLO_TAGS, 'description' => SLO_DESCRIPTION
end

describe '#update_service_level_objective' do
it_behaves_like 'an api method',
:update_service_level_objective, [SLO_ID, SLO_TYPE, { name: SLO_NAME }],
:put, "/slo/#{SLO_ID}", 'type' => SLO_TYPE, 'name' => SLO_NAME
end

describe '#get_service_level_objective' do
it_behaves_like 'an api method',
:get_service_level_objective, [SLO_ID],
:get, "/slo/#{SLO_ID}"
end

describe '#get_service_level_objective_history' do
it_behaves_like 'an api method with params',
:get_service_level_objective_history, [SLO_ID],
:get, "/slo/#{SLO_ID}/history", 'from_ts' => 0, 'to_ts' => 1_000_000
end

describe '#can_delete_service_level_objective' do
it_behaves_like 'an api method with params',
:can_delete_service_level_objective, [],
:get, '/slo/can_delete', 'ids' => [SLO_ID]
end

describe '#search_service_level_objective' do
it_behaves_like 'an api method with optional params',
:search_service_level_objective, [[SLO_ID]],
:get, '/slo/', 'ids' => SLO_ID
end

describe '#delete_service_level_objective' do
it_behaves_like 'an api method',
:delete_service_level_objective, [SLO_ID],
:delete, "/slo/#{SLO_ID}"
end

describe '#delete_many_service_level_objective' do
it_behaves_like 'an api method',
:delete_many_service_level_objective, [[SLO_ID]],
:delete, '/slo/', 'ids' => [SLO_ID]
end

describe '#delete_timeframes_service_level_objective' do
it_behaves_like 'an api method',
:delete_timeframes_service_level_objective, [{ SLO_ID => ['7d'] }],
:post, '/slo/bulk_delete', SLO_ID => ['7d']
end
end