Commit d40b13bc authored by Patrick Bair's avatar Patrick Bair

Merge branch '340364-create-agent-activity-events' into 'master'

Create agent activity events

See merge request gitlab-org/gitlab!74577
parents 5fc85146 f4dc57f6
......@@ -16,6 +16,8 @@ module Clusters
has_many :project_authorizations, class_name: 'Clusters::Agents::ProjectAuthorization'
has_many :authorized_projects, class_name: '::Project', through: :project_authorizations, source: :project
has_many :activity_events, -> { in_timeline_order }, class_name: 'Clusters::Agents::ActivityEvent', inverse_of: :agent
scope :ordered_by_name, -> { order(:name) }
scope :with_name, -> (name) { where(name: name) }
......
# frozen_string_literal: true
module Clusters
module Agents
class ActivityEvent < ApplicationRecord
include NullifyIfBlank
self.table_name = 'agent_activity_events'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :user
belongs_to :agent_token, class_name: 'Clusters::AgentToken'
scope :in_timeline_order, -> { order(recorded_at: :desc, id: :desc) }
validates :recorded_at, :kind, :level, presence: true
nullify_if_blank :detail
enum kind: {
token_created: 0
}, _prefix: true
enum level: {
debug: 0,
info: 1,
warn: 2,
error: 3,
fatal: 4,
unknown: 5
}, _prefix: true
end
end
end
......@@ -11,6 +11,8 @@ module Clusters
token = ::Clusters::AgentToken.new(filtered_params.merge(created_by_user: current_user))
if token.save
log_activity_event!(token)
ServiceResponse.success(payload: { secret: token.token, token: token })
else
ServiceResponse.error(message: token.errors.full_messages)
......@@ -26,6 +28,16 @@ module Clusters
def filtered_params
params.slice(*ALLOWED_PARAMS)
end
def log_activity_event!(token)
token.agent.activity_events.create!(
kind: :token_created,
level: :info,
recorded_at: token.created_at,
user: current_user,
agent_token: token
)
end
end
end
end
# frozen_string_literal: true
class CreateAgentActivityEvents < Gitlab::Database::Migration[1.0]
def change
create_table :agent_activity_events do |t|
t.bigint :agent_id, null: false
t.bigint :user_id, index: { where: 'user_id IS NOT NULL' }
t.bigint :project_id, index: { where: 'project_id IS NOT NULL' }
t.bigint :merge_request_id, index: { where: 'merge_request_id IS NOT NULL' }
t.bigint :agent_token_id, index: { where: 'agent_token_id IS NOT NULL' }
t.datetime_with_timezone :recorded_at, null: false
t.integer :kind, limit: 2, null: false
t.integer :level, limit: 2, null: false
t.binary :sha
t.text :detail, limit: 255
t.index [:agent_id, :recorded_at, :id]
end
end
end
# frozen_string_literal: true
class AddAgentActivityEventsForeignKeys < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :agent_activity_events, :cluster_agents, column: :agent_id, on_delete: :cascade
add_concurrent_foreign_key :agent_activity_events, :users, column: :user_id, on_delete: :nullify
add_concurrent_foreign_key :agent_activity_events, :projects, column: :project_id, on_delete: :nullify
add_concurrent_foreign_key :agent_activity_events, :merge_requests, column: :merge_request_id, on_delete: :nullify
add_concurrent_foreign_key :agent_activity_events, :cluster_agent_tokens, column: :agent_token_id, on_delete: :nullify
end
def down
with_lock_retries do
remove_foreign_key_if_exists :agent_activity_events, column: :agent_id
end
with_lock_retries do
remove_foreign_key_if_exists :agent_activity_events, column: :user_id
end
with_lock_retries do
remove_foreign_key_if_exists :agent_activity_events, column: :project_id
end
with_lock_retries do
remove_foreign_key_if_exists :agent_activity_events, column: :merge_request_id
end
with_lock_retries do
remove_foreign_key_if_exists :agent_activity_events, column: :agent_token_id
end
end
end
1c5f65a25c9cf81a50bd9ffa2e74e2621cff04e58a2f90b19c66741ebb459d3e
\ No newline at end of file
4038c269ce9c47ca9327fb1b81bb588e9065f0821f291d17c7965d7f8fe1f275
\ No newline at end of file
......@@ -9721,6 +9721,30 @@ CREATE SEQUENCE abuse_reports_id_seq
ALTER SEQUENCE abuse_reports_id_seq OWNED BY abuse_reports.id;
CREATE TABLE agent_activity_events (
id bigint NOT NULL,
agent_id bigint NOT NULL,
user_id bigint,
project_id bigint,
merge_request_id bigint,
agent_token_id bigint,
recorded_at timestamp with time zone NOT NULL,
kind smallint NOT NULL,
level smallint NOT NULL,
sha bytea,
detail text,
CONSTRAINT check_068205e735 CHECK ((char_length(detail) <= 255))
);
CREATE SEQUENCE agent_activity_events_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE agent_activity_events_id_seq OWNED BY agent_activity_events.id;
CREATE TABLE agent_group_authorizations (
id bigint NOT NULL,
group_id bigint NOT NULL,
......@@ -21064,6 +21088,8 @@ ALTER SEQUENCE zoom_meetings_id_seq OWNED BY zoom_meetings.id;
ALTER TABLE ONLY abuse_reports ALTER COLUMN id SET DEFAULT nextval('abuse_reports_id_seq'::regclass);
ALTER TABLE ONLY agent_activity_events ALTER COLUMN id SET DEFAULT nextval('agent_activity_events_id_seq'::regclass);
ALTER TABLE ONLY agent_group_authorizations ALTER COLUMN id SET DEFAULT nextval('agent_group_authorizations_id_seq'::regclass);
ALTER TABLE ONLY agent_project_authorizations ALTER COLUMN id SET DEFAULT nextval('agent_project_authorizations_id_seq'::regclass);
......@@ -22412,6 +22438,9 @@ ALTER TABLE ONLY gitlab_partitions_static.product_analytics_events_experimental_
ALTER TABLE ONLY abuse_reports
ADD CONSTRAINT abuse_reports_pkey PRIMARY KEY (id);
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT agent_activity_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY agent_group_authorizations
ADD CONSTRAINT agent_group_authorizations_pkey PRIMARY KEY (id);
......@@ -24907,6 +24936,16 @@ CREATE UNIQUE INDEX idx_vulnerability_issue_links_on_vulnerability_id_and_link_t
CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id);
CREATE INDEX index_agent_activity_events_on_agent_id_and_recorded_at_and_id ON agent_activity_events USING btree (agent_id, recorded_at, id);
CREATE INDEX index_agent_activity_events_on_agent_token_id ON agent_activity_events USING btree (agent_token_id) WHERE (agent_token_id IS NOT NULL);
CREATE INDEX index_agent_activity_events_on_merge_request_id ON agent_activity_events USING btree (merge_request_id) WHERE (merge_request_id IS NOT NULL);
CREATE INDEX index_agent_activity_events_on_project_id ON agent_activity_events USING btree (project_id) WHERE (project_id IS NOT NULL);
CREATE INDEX index_agent_activity_events_on_user_id ON agent_activity_events USING btree (user_id) WHERE (user_id IS NOT NULL);
CREATE UNIQUE INDEX index_agent_group_authorizations_on_agent_id_and_group_id ON agent_group_authorizations USING btree (agent_id, group_id);
CREATE INDEX index_agent_group_authorizations_on_group_id ON agent_group_authorizations USING btree (group_id);
......@@ -28803,6 +28842,9 @@ ALTER TABLE ONLY import_failures
ALTER TABLE ONLY project_ci_cd_settings
ADD CONSTRAINT fk_24c15d2f2e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT fk_256c631779 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL;
ALTER TABLE ONLY epics
ADD CONSTRAINT fk_25b99c1be3 FOREIGN KEY (parent_id) REFERENCES epics(id) ON DELETE CASCADE;
......@@ -28875,6 +28917,9 @@ ALTER TABLE ONLY bulk_import_exports
ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_3a9eaa254d FOREIGN KEY (stage_id) REFERENCES ci_stages(id) ON DELETE CASCADE;
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT fk_3af186389b FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE SET NULL;
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_3b8c72ea56 FOREIGN KEY (sprint_id) REFERENCES sprints(id) ON DELETE SET NULL;
......@@ -29319,6 +29364,12 @@ ALTER TABLE ONLY geo_event_log
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_c63cbf6c25 FOREIGN KEY (closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT fk_c815368376 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT fk_c8b006d40f FOREIGN KEY (agent_token_id) REFERENCES cluster_agent_tokens(id) ON DELETE SET NULL;
ALTER TABLE ONLY issue_links
ADD CONSTRAINT fk_c900194ff2 FOREIGN KEY (source_id) REFERENCES issues(id) ON DELETE CASCADE;
......@@ -29373,6 +29424,9 @@ ALTER TABLE ONLY geo_event_log
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_d6cf4279f7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY agent_activity_events
ADD CONSTRAINT fk_d6f785c9fc FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY metrics_users_starred_dashboards
ADD CONSTRAINT fk_d76a2b9a8c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
abuse_reports: :gitlab_main
agent_activity_events: :gitlab_main
agent_group_authorizations: :gitlab_main
agent_project_authorizations: :gitlab_main
alert_management_alert_assignees: :gitlab_main
......
# frozen_string_literal: true
FactoryBot.define do
factory :agent_activity_event, class: 'Clusters::Agents::ActivityEvent' do
association :agent, factory: :cluster_agent
association :agent_token, factory: :cluster_agent_token
user
kind { :token_created }
level { :info }
recorded_at { Time.current }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Agents::ActivityEvent do
it { is_expected.to belong_to(:agent).class_name('Clusters::Agent').required }
it { is_expected.to belong_to(:user).optional }
it { is_expected.to belong_to(:agent_token).class_name('Clusters::AgentToken').optional }
it { is_expected.to validate_presence_of(:kind) }
it { is_expected.to validate_presence_of(:level) }
it { is_expected.to validate_presence_of(:recorded_at) }
it { is_expected.to nullify_if_blank(:detail) }
describe 'scopes' do
let_it_be(:agent) { create(:cluster_agent) }
describe '.in_timeline_order' do
let(:recorded_at) { 1.hour.ago }
let!(:event1) { create(:agent_activity_event, agent: agent, recorded_at: recorded_at) }
let!(:event2) { create(:agent_activity_event, agent: agent, recorded_at: Time.current) }
let!(:event3) { create(:agent_activity_event, agent: agent, recorded_at: recorded_at) }
subject { described_class.in_timeline_order }
it 'sorts by recorded_at: :desc, id: :desc' do
is_expected.to eq([event2, event3, event1])
end
end
end
end
......@@ -47,6 +47,21 @@ RSpec.describe Clusters::AgentTokens::CreateService do
expect(token.name).to eq(params[:name])
end
it 'creates an activity event' do
expect { subject }.to change { ::Clusters::Agents::ActivityEvent.count }.by(1)
token = subject.payload[:token].reload
event = cluster_agent.activity_events.last
expect(event).to have_attributes(
kind: 'token_created',
level: 'info',
recorded_at: token.created_at,
user: token.created_by_user,
agent_token: token
)
end
context 'when params are invalid' do
let(:params) { { agent_id: 'bad_id' } }
......@@ -54,6 +69,10 @@ RSpec.describe Clusters::AgentTokens::CreateService do
expect { subject }.not_to change(::Clusters::AgentToken, :count)
end
it 'does not create an activity event' do
expect { subject }.not_to change { ::Clusters::Agents::ActivityEvent.count }
end
it 'returns validation errors', :aggregate_failures do
expect(subject.status).to eq(:error)
expect(subject.message).to eq(["Agent must exist", "Name can't be blank"])
......
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