Commit 04b6ff27 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '322128-update-token-used-field' into 'master'

Track agent token last_used

See merge request gitlab-org/gitlab!56143
parents 915b42b7 1380b21f
...@@ -2,11 +2,17 @@ ...@@ -2,11 +2,17 @@
module Clusters module Clusters
class AgentToken < ApplicationRecord class AgentToken < ApplicationRecord
include RedisCacheable
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :token, encrypted: :required, token_generator: -> { Devise.friendly_token(50) } add_authentication_token_field :token, encrypted: :required, token_generator: -> { Devise.friendly_token(50) }
cached_attr_reader :last_contacted_at
self.table_name = 'cluster_agent_tokens' self.table_name = 'cluster_agent_tokens'
# The `UPDATE_USED_COLUMN_EVERY` defines how often the token DB entry can be updated
UPDATE_USED_COLUMN_EVERY = (40.minutes..55.minutes).freeze
belongs_to :agent, class_name: 'Clusters::Agent', optional: false belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :created_by_user, class_name: 'User', optional: true belongs_to :created_by_user, class_name: 'User', optional: true
...@@ -14,5 +20,27 @@ module Clusters ...@@ -14,5 +20,27 @@ module Clusters
validates :description, length: { maximum: 1024 } validates :description, length: { maximum: 1024 }
validates :name, presence: true, length: { maximum: 255 }, on: :create validates :name, presence: true, length: { maximum: 255 }, on: :create
def track_usage
track_values = { last_used_at: Time.current.utc }
cache_attributes(track_values)
# Use update_column so updated_at is skipped
update_columns(track_values) if can_update_track_values?
end
private
def can_update_track_values?
# Use a random threshold to prevent beating DB updates.
last_used_at_max_age = Random.rand(UPDATE_USED_COLUMN_EVERY)
real_last_used_at = read_attribute(:last_used_at)
# Handle too many updates from high token traffic
real_last_used_at.nil? ||
(Time.current - real_last_used_at) >= last_used_at_max_age
end
end end
end end
---
title: Track agent token last_used
merge_request: 56143
author:
type: added
...@@ -1328,6 +1328,7 @@ An edge in a connection. ...@@ -1328,6 +1328,7 @@ An edge in a connection.
| `createdByUser` | [`User`](#user) | The user who created the token. | | `createdByUser` | [`User`](#user) | The user who created the token. |
| `description` | [`String`](#string) | Description of the token. | | `description` | [`String`](#string) | Description of the token. |
| `id` | [`ClustersAgentTokenID!`](#clustersagenttokenid) | Global ID of the token. | | `id` | [`ClustersAgentTokenID!`](#clustersagenttokenid) | Global ID of the token. |
| `lastUsedAt` | [`Time`](#time) | Timestamp the token was last used. |
| `name` | [`String`](#string) | Name given to the token. | | `name` | [`String`](#string) | Name given to the token. |
### `ClusterAgentTokenConnection` ### `ClusterAgentTokenConnection`
......
...@@ -29,6 +29,11 @@ module Types ...@@ -29,6 +29,11 @@ module Types
null: true, null: true,
description: 'Description of the token.' description: 'Description of the token.'
field :last_used_at,
Types::TimeType,
null: true,
description: 'Timestamp the token was last used.'
field :id, field :id,
::Types::GlobalIDType[::Clusters::AgentToken], ::Types::GlobalIDType[::Clusters::AgentToken],
null: false, null: false,
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['ClusterAgentToken'] do RSpec.describe GitlabSchema.types['ClusterAgentToken'] do
let(:fields) { %i[cluster_agent created_at created_by_user description id name] } let(:fields) { %i[cluster_agent created_at created_by_user description id last_used_at name] }
it { expect(described_class.graphql_name).to eq('ClusterAgentToken') } it { expect(described_class.graphql_name).to eq('ClusterAgentToken') }
......
...@@ -54,6 +54,8 @@ module API ...@@ -54,6 +54,8 @@ module API
forbidden! unless agent_token forbidden! unless agent_token
forbidden! unless Gitlab::Kas.included_in_gitlab_com_rollout?(agent.project) forbidden! unless Gitlab::Kas.included_in_gitlab_com_rollout?(agent.project)
agent_token.track_usage
end end
end end
......
...@@ -24,4 +24,53 @@ RSpec.describe Clusters::AgentToken do ...@@ -24,4 +24,53 @@ RSpec.describe Clusters::AgentToken do
expect(agent_token.token.length).to be >= 50 expect(agent_token.token.length).to be >= 50
end end
end end
describe '#track_usage', :clean_gitlab_redis_cache do
let(:agent_token) { create(:cluster_agent_token) }
subject { agent_token.track_usage }
context 'when last_used_at was updated recently' do
before do
agent_token.update!(last_used_at: 10.minutes.ago)
end
it 'updates cache but not database' do
expect { subject }.not_to change { agent_token.reload.read_attribute(:last_used_at) }
expect_redis_update
end
end
context 'when last_used_at was not updated recently' do
it 'updates cache and database' do
does_db_update
expect_redis_update
end
context 'with invalid token' do
before do
agent_token.description = SecureRandom.hex(2000)
end
it 'still updates caches and database' do
expect(agent_token).to be_invalid
does_db_update
expect_redis_update
end
end
end
def expect_redis_update
Gitlab::Redis::Cache.with do |redis|
redis_key = "cache:#{described_class.name}:#{agent_token.id}:attributes"
expect(redis.get(redis_key)).to be_present
end
end
def does_db_update
expect { subject }.to change { agent_token.reload.read_attribute(:last_used_at) }
end
end
end end
...@@ -51,6 +51,12 @@ RSpec.describe API::Internal::Kubernetes do ...@@ -51,6 +51,12 @@ RSpec.describe API::Internal::Kubernetes do
end end
end end
shared_examples 'agent token tracking' do
it 'tracks token usage' do
expect { response }.to change { agent_token.reload.read_attribute(:last_used_at) }
end
end
describe 'POST /internal/kubernetes/usage_metrics' do describe 'POST /internal/kubernetes/usage_metrics' do
def send_request(headers: {}, params: {}) def send_request(headers: {}, params: {})
post api('/internal/kubernetes/usage_metrics'), params: params, headers: headers.reverse_merge(jwt_auth_headers) post api('/internal/kubernetes/usage_metrics'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
...@@ -101,6 +107,8 @@ RSpec.describe API::Internal::Kubernetes do ...@@ -101,6 +107,8 @@ RSpec.describe API::Internal::Kubernetes do
let(:agent) { agent_token.agent } let(:agent) { agent_token.agent }
let(:project) { agent.project } let(:project) { agent.project }
shared_examples 'agent token tracking'
it 'returns expected data', :aggregate_failures do it 'returns expected data', :aggregate_failures do
send_request(headers: { 'Authorization' => "Bearer #{agent_token.token}" }) send_request(headers: { 'Authorization' => "Bearer #{agent_token.token}" })
...@@ -169,6 +177,8 @@ RSpec.describe API::Internal::Kubernetes do ...@@ -169,6 +177,8 @@ RSpec.describe API::Internal::Kubernetes do
context 'an agent is found' do context 'an agent is found' do
let_it_be(:agent_token) { create(:cluster_agent_token) } let_it_be(:agent_token) { create(:cluster_agent_token) }
shared_examples 'agent token tracking'
context 'project is public' do context 'project is public' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
......
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