Commit 16ac0111 authored by Thong Kuah's avatar Thong Kuah

New internal endpoint for kas to increment gitop_sync metric

This metric will then be pushed onto a Redis counter. The redis counter
will then be posted to usage ping later.

DRY up specs for common authorization problems

Add kubernetes_agent_gitops_sync to usage ping
parent 5be9526c
---
title: Add kubernetes_agent_gitops_sync usage ping metric
merge_request: 40568
author:
type: other
......@@ -96,6 +96,25 @@ module API
gitaly_repository: gitaly_repository(project)
}
end
desc 'POST usage metrics' do
detail 'Updates usage metrics for agent'
end
route_setting :authentication, cluster_agent_token_allowed: true
params do
requires :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
end
post '/usage_metrics' do
gitops_sync_count = params[:gitops_sync_count]
if gitops_sync_count < 0
bad_request!('gitops_sync_count must be greater than or equal to zero')
else
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_gitops_sync(gitops_sync_count)
no_content!
end
end
end
end
end
......
......@@ -245,7 +245,8 @@ module Gitlab
Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
Gitlab::UsageDataCounters::SourceCodeCounter,
Gitlab::UsageDataCounters::MergeRequestCounter,
Gitlab::UsageDataCounters::DesignsCounter
Gitlab::UsageDataCounters::DesignsCounter,
Gitlab::UsageDataCounters::KubernetesAgentCounter
]
end
......
# frozen_string_literal: true
module Gitlab
module UsageDataCounters
class KubernetesAgentCounter < BaseCounter
PREFIX = 'kubernetes_agent'
KNOWN_EVENTS = %w[gitops_sync].freeze
class << self
def increment_gitops_sync(incr)
raise ArgumentError, 'must be greater than or equal to zero' if incr < 0
# rather then hitting redis for this no-op, we return early
# note: redis returns the increment, so we mimic this here
return 0 if incr == 0
increment_by(redis_key(:gitops_sync), incr)
end
end
end
end
end
......@@ -9,6 +9,12 @@ module Gitlab
Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) }
end
def increment_by(redis_counter_key, incr)
return unless Gitlab::CurrentSettings.usage_ping_enabled
Gitlab::Redis::SharedState.with { |redis| redis.incrby(redis_counter_key, incr) }
end
def total_count(redis_counter_key)
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_counter_key).to_i }
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::KubernetesAgentCounter do
it_behaves_like 'a redis usage counter', 'Kubernetes Agent', :gitops_sync
it_behaves_like 'a redis usage counter with totals', :kubernetes_agent, gitops_sync: 1
describe '.increment_gitops_sync' do
it 'increments the gtops_sync counter by the new increment amount' do
described_class.increment_gitops_sync(7)
described_class.increment_gitops_sync(2)
described_class.increment_gitops_sync(0)
expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 9)
end
it 'raises for negative numbers' do
expect { described_class.increment_gitops_sync(-1) }.to raise_error(ArgumentError)
end
end
end
......@@ -11,6 +11,7 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar
stub_application_setting(usage_ping_enabled: setting_value)
end
describe '.increment' do
context 'when usage_ping is disabled' do
let(:setting_value) { false }
......@@ -30,4 +31,27 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar
end.to change { subject.total_count(redis_key) }.by(1)
end
end
end
describe '.increment_by' do
context 'when usage_ping is disabled' do
let(:setting_value) { false }
it 'counter is not increased' do
expect do
subject.increment_by(redis_key, 3)
end.not_to change { subject.total_count(redis_key) }
end
end
context 'when usage_ping is enabled' do
let(:setting_value) { true }
it 'counter is increased' do
expect do
subject.increment_by(redis_key, 3)
end.to change { subject.total_count(redis_key) }.by(3)
end
end
end
end
......@@ -575,6 +575,12 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
describe '.usage_counters' do
subject { described_class.usage_counters }
it { is_expected.to include(:kubernetes_agent_gitops_sync) }
end
describe '.usage_data_counters' do
subject { described_class.usage_data_counters }
......
......@@ -15,11 +15,7 @@ RSpec.describe API::Internal::Kubernetes do
allow(Gitlab::Kas).to receive(:secret).and_return(jwt_secret)
end
describe "GET /internal/kubernetes/agent_info" do
def send_request(headers: {}, params: {})
get api('/internal/kubernetes/agent_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
shared_examples 'authorization' do
context 'not authenticated' do
it 'returns 401' do
send_request(headers: { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => '' })
......@@ -28,6 +24,20 @@ RSpec.describe API::Internal::Kubernetes do
end
end
context 'authenticated' do
it 'returns 403 if Authorization header not sent' do
send_request
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns 404 if Authorization is for non-existent agent' do
send_request(headers: { 'Authorization' => 'Bearer NONEXISTENT' })
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'kubernetes_agent_internal_api feature flag disabled' do
before do
stub_feature_flags(kubernetes_agent_internal_api: false)
......@@ -39,13 +49,51 @@ RSpec.describe API::Internal::Kubernetes do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
it 'returns 403 if Authorization header not sent' do
send_request
describe 'POST /internal/kubernetes/usage_metrics' do
def send_request(headers: {}, params: {})
post api('/internal/kubernetes/usage_metrics'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
expect(response).to have_gitlab_http_status(:forbidden)
include_examples 'authorization'
context 'is authenticated for an agent' do
let!(:agent_token) { create(:cluster_agent_token) }
it 'returns no_content for valid gitops_sync_count' do
send_request(params: { gitops_sync_count: 10 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:no_content)
end
it 'returns no_content 0 gitops_sync_count' do
send_request(params: { gitops_sync_count: 0 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:no_content)
end
it 'returns 400 for non number' do
send_request(params: { gitops_sync_count: 'string' }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 for negative number' do
send_request(params: { gitops_sync_count: '-1' }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
describe "GET /internal/kubernetes/agent_info" do
def send_request(headers: {}, params: {})
get api('/internal/kubernetes/agent_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
include_examples 'authorization'
context 'an agent is found' do
let!(:agent_token) { create(:cluster_agent_token) }
......@@ -77,14 +125,6 @@ RSpec.describe API::Internal::Kubernetes do
)
end
end
context 'no such agent exists' do
it 'returns 404' do
send_request(headers: { 'Authorization' => 'Bearer ABCD' })
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe 'GET /internal/kubernetes/project_info' do
......@@ -92,39 +132,7 @@ RSpec.describe API::Internal::Kubernetes do
get api('/internal/kubernetes/project_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
end
context 'not authenticated' do
it 'returns 401' do
send_request(headers: { Gitlab::Kas::INTERNAL_API_REQUEST_HEADER => '' })
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'kubernetes_agent_internal_api feature flag disabled' do
before do
stub_feature_flags(kubernetes_agent_internal_api: false)
end
it 'returns 404' do
send_request
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'returns 403 if Authorization header not sent' do
send_request
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'no such agent exists' do
it 'returns 404' do
send_request(headers: { 'Authorization' => 'Bearer ABCD' })
expect(response).to have_gitlab_http_status(:forbidden)
end
end
include_examples 'authorization'
context 'an agent is found' do
let!(:agent_token) { create(:cluster_agent_token) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment