From c54ccb47fa6ebd6c3f3a101c60d2be23ef5fa324 Mon Sep 17 00:00:00 2001 From: Mariia Solodovnik Date: Mon, 20 Mar 2023 16:55:46 +0200 Subject: [PATCH 1/2] Add GeoSite and GeoSiteStatus grape entities --- ee/lib/ee/api/entities/geo_site.rb | 61 +++++++ ee/lib/ee/api/entities/geo_site_status.rb | 101 ++++++++++++ .../ee/api/entities/geo_site_status_spec.rb | 150 ++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 ee/lib/ee/api/entities/geo_site.rb create mode 100644 ee/lib/ee/api/entities/geo_site_status.rb create mode 100644 ee/spec/lib/ee/api/entities/geo_site_status_spec.rb diff --git a/ee/lib/ee/api/entities/geo_site.rb b/ee/lib/ee/api/entities/geo_site.rb new file mode 100644 index 00000000000000..bc760a075e27c3 --- /dev/null +++ b/ee/lib/ee/api/entities/geo_site.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module EE + module API + module Entities + class GeoSite < Grape::Entity + include ::API::Helpers::RelatedResourcesHelpers + + expose :id + expose :name + expose :url + expose :internal_url + expose :primary?, as: :primary + expose :enabled + expose :files_max_capacity + expose :repos_max_capacity + expose :verification_max_capacity + expose :container_repositories_max_capacity + expose :selective_sync_type + expose :selective_sync_shards + expose :namespace_ids, as: :selective_sync_namespace_ids + expose :minimum_reverification_interval + expose :sync_object_storage, if: ->(geo_node, _) { geo_node.secondary? } + + # Retained for backwards compatibility. Remove in API v5 + expose :clone_protocol do |_record, _options| + 'http' + end + + expose :web_edit_url do |geo_node| + ::Gitlab::Routing.url_helpers.edit_admin_geo_node_url(geo_node) + end + + # @deprecated in favor of web_geo_replication_details_url + expose :web_geo_projects_url, if: ->(geo_node) { geo_node.secondary? }, + proc: ->(geo_node) { geo_node.geo_projects_url } + + expose :web_geo_replication_details_url, if: ->(geo_node) { geo_node.secondary? }, + proc: ->(geo_node) { geo_node.geo_replication_details_url } + + expose :_links do + expose :self do |geo_node| + expose_url api_v4_geo_nodes_path(id: geo_node.id) + end + + expose :status do |geo_node| + expose_url api_v4_geo_nodes_status_path(id: geo_node.id) + end + + expose :repair do |geo_node| + expose_url api_v4_geo_nodes_repair_path(id: geo_node.id) + end + end + + expose :current do |geo_node| + ::GeoNode.current?(geo_node) + end + end + end + end +end diff --git a/ee/lib/ee/api/entities/geo_site_status.rb b/ee/lib/ee/api/entities/geo_site_status.rb new file mode 100644 index 00000000000000..5314c266bf48fa --- /dev/null +++ b/ee/lib/ee/api/entities/geo_site_status.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module EE + module API + module Entities + class GeoSiteStatus < Grape::Entity + include ::API::Helpers::RelatedResourcesHelpers + include ActionView::Helpers::NumberHelper + + expose :geo_node_id + + ::GeoNodeStatus::RESOURCE_STATUS_FIELDS.each do |field| + expose field + end + + ::GeoNodeStatus.percentage_methods.each do |method_name| + expose method_name do |node| + number_to_percentage(node[method_name], precision: 2) + end + end + + # Aliased from :project_count + expose :repositories_count + expose :wikis_count + + expose :replication_slots_count + expose :replication_slots_used_count + + expose :healthy?, as: :healthy + expose :health do |node| + node.healthy? ? 'Healthy' : node.health + end + expose :health_status + expose :missing_oauth_application + + expose :db_replication_lag_seconds + + expose :container_repositories_replication_enabled + expose :design_repositories_replication_enabled + expose :repositories_replication_enabled + + expose :repository_verification_enabled + + expose :replication_slots_max_retained_wal_bytes + + expose :repositories_checked_count + expose :repositories_checked_failed_count + + expose :last_event_id + expose :last_event_timestamp + expose :cursor_last_event_id + expose :cursor_last_event_timestamp + + expose :last_successful_status_check_timestamp + + expose :version + expose :revision + + expose :selective_sync_type + + # Deprecated: remove in API v5. We use selective_sync_type instead now. + expose :namespaces, using: ::API::Entities::NamespaceBasic + + expose :updated_at + + # We load GeoSiteStatus data in two ways: + # + # 1. Directly by asking a Geo site via an API call + # 2. Via cached state in the database + # + # We don't yet cached the state of the shard information in the database, so if + # we don't have this information omit from the serialization entirely. + expose :storage_shards, using: StorageShardEntity, if: ->(status, _options) do + status.storage_shards.present? + end + + expose :storage_shards_match?, as: :storage_shards_match + + expose :_links do + expose :self do |geo_node_status| + expose_url api_v4_geo_nodes_status_path(id: geo_node_status.geo_node_id) + end + + expose :node do |geo_node_status| + expose_url api_v4_geo_nodes_path(id: geo_node_status.geo_node_id) + end + end + + private + + def namespaces + object.geo_node.namespaces + end + + def missing_oauth_application + object.geo_node.missing_oauth_application? + end + end + end + end +end diff --git a/ee/spec/lib/ee/api/entities/geo_site_status_spec.rb b/ee/spec/lib/ee/api/entities/geo_site_status_spec.rb new file mode 100644 index 00000000000000..817aafd4056a4e --- /dev/null +++ b/ee/spec/lib/ee/api/entities/geo_site_status_spec.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe EE::API::Entities::GeoSiteStatus, feature_category: :geo_replication do + include ::EE::GeoHelpers + + let!(:geo_node_status) { build(:geo_node_status) } + let(:entity) { described_class.new(geo_node_status, request: double) } + let(:error) { 'Could not connect to Geo database' } + + subject { entity.as_json } + + before do + stub_primary_node + end + + describe '#healthy' do + context 'when site is healthy' do + it 'returns true' do + expect(subject[:healthy]).to eq true + end + end + + context 'when site is unhealthy' do + before do + geo_node_status.status_message = error + end + + subject { entity.as_json } + + it 'returns false' do + expect(subject[:healthy]).to eq false + end + end + end + + describe '#health' do + context 'when site is healthy' do + it 'exposes the health message' do + expect(subject[:health]).to eq GeoNodeStatus::HEALTHY_STATUS + end + end + + context 'when site is unhealthy' do + before do + geo_node_status.status_message = error + end + + subject { entity.as_json } + + it 'exposes the error message' do + expect(subject[:health]).to eq error + end + end + end + + describe '#job_artifacts_synced_in_percentage' do + it 'formats as percentage' do + geo_node_status.assign_attributes( + job_artifacts_registry_count: 256, + job_artifacts_failed_count: 12, + job_artifacts_synced_count: 123 + ) + + expect(subject[:job_artifacts_synced_in_percentage]).to eq '48.05%' + end + end + + describe '#container_repositories_synced_in_percentage' do + it 'formats as percentage' do + geo_node_status.assign_attributes( + container_repositories_registry_count: 256, + container_repositories_failed_count: 12, + container_repositories_synced_count: 123 + ) + + expect(subject[:container_repositories_synced_in_percentage]).to eq '48.05%' + end + end + + describe '#design_repositories_synced_in_percentage' do + it 'formats as percentage' do + geo_node_status.assign_attributes( + design_repositories_count: 256, + design_repositories_failed_count: 12, + design_repositories_synced_count: 123 + ) + + expect(subject[:design_repositories_synced_in_percentage]).to eq '48.05%' + end + end + + describe '#repositories_synced_in_percentage' do + it 'formats as percentage' do + geo_node_status.assign_attributes( + projects_count: 10, + repositories_synced_count: 5, + repositories_failed_count: 0 + ) + + expect(subject[:repositories_synced_in_percentage]).to eq '50.00%' + end + end + + describe '#replication_slots_used_in_percentage' do + it 'formats as percentage' do + geo_node_status.assign_attributes( + replication_slots_count: 4, + replication_slots_used_count: 2 + ) + + expect(subject[:replication_slots_used_in_percentage]).to eq '50.00%' + end + end + + describe '#namespaces' do + it 'returns empty array when full sync is active' do + expect(subject[:namespaces]).to be_empty + end + + it 'returns array of namespace ids and paths for selective sync' do + namespace = create(:namespace) + geo_node_status.geo_node.namespaces << namespace + + expect(subject[:namespaces]).not_to be_empty + expect(subject[:namespaces]).to be_an(Array) + expect(subject[:namespaces].first[:id]).to eq(namespace.id) + expect(subject[:namespaces].first[:path]).to eq(namespace.path) + end + end + + describe '#storage_shards' do + it 'returns the config' do + shards = StorageShard.all + + expect(subject[:storage_shards].count).to eq(shards.count) + expect(subject[:storage_shards].first[:name]).to eq(shards.first.name) + end + end + + context 'when secondary Geo site' do + before do + stub_secondary_node + end + + it { is_expected.to have_key(:storage_shards) } + it { is_expected.to have_key(:storage_shards_match) } + end +end -- GitLab From 713233bef154af91271e64b5ed2971973f560640 Mon Sep 17 00:00:00 2001 From: Mariia Solodovnik Date: Mon, 27 Mar 2023 12:31:45 +0300 Subject: [PATCH 2/2] Add geo_site spec --- ee/spec/lib/ee/api/entities/geo_site_spec.rb | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ee/spec/lib/ee/api/entities/geo_site_spec.rb diff --git a/ee/spec/lib/ee/api/entities/geo_site_spec.rb b/ee/spec/lib/ee/api/entities/geo_site_spec.rb new file mode 100644 index 00000000000000..7374a65928959c --- /dev/null +++ b/ee/spec/lib/ee/api/entities/geo_site_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe EE::API::Entities::GeoSite, feature_category: :geo_replication do + include ::EE::GeoHelpers + + let!(:geo_node) { create(:geo_node) } + let(:entity) { described_class.new(geo_node, request: double) } + let(:error) { 'Could not connect to Geo database' } + + subject { entity.as_json } + + before do + stub_primary_node + end + + describe '#clone_protocol' do + it { expect(subject[:clone_protocol]).to eq 'http' } + end + + describe '#web_edit_url' do + it { expect(subject[:web_edit_url]).to eq Gitlab::Routing.url_helpers.edit_admin_geo_node_url(geo_node) } + end + + describe '#self' do + it { expect(subject[:_links][:self]).to eq expose_url(api_v4_geo_nodes_path(id: geo_node.id)) } + end + + describe '#status' do + it { expect(subject[:_links][:status]).to eq expose_url(api_v4_geo_nodes_status_path(id: geo_node.id)) } + end + + describe '#repair' do + it { expect(subject[:_links][:repair]).to eq expose_url(api_v4_geo_nodes_repair_path(id: geo_node.id)) } + end + + describe '#current' do + context 'when node is current' do + before do + allow(Gitlab.config.geo).to receive(:node_name).and_return geo_node.name + end + + it { expect(subject[:current]).to eq true } + end + + context 'when node is not current' do + before do + allow(Gitlab.config.geo).to receive(:node_name).and_return 'test name' + end + + it { expect(subject[:current]).to eq false } + end + end +end -- GitLab