Commit f78af0fc authored by Oswaldo Ferreira's avatar Oswaldo Ferreira

Add cop to encourage idempotent Sidekiq workers

This commit's goal is to add a cop (Scalability::IdempotentWorker)
that will catch all workers that don't call idempotent! in its scope.

Calling it will label the worker as idempotent, which will end up in
our Sidekiq logs for further visibility.

This also introduces a shared example and a perform_multiple helper
method for executing jobs multiple times through unit tests.

Also in this commit we set an example in an already idempotent worker
ExpireJobCacheWorker. For this worker we call idempotent! in its
scope and add tests with the new test helpers.
parent f378b397
# frozen_string_literal: true
class AdminEmailWorker
class AdminEmailWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
......
This diff is collapsed.
# frozen_string_literal: true
class ArchiveTraceWorker
class ArchiveTraceWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineBackgroundQueue
......
# frozen_string_literal: true
class AuthorizedProjectsWorker
class AuthorizedProjectsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
prepend WaitableWorker
......
# frozen_string_literal: true
module AutoDevops
class DisableWorker
class DisableWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include AutoDevopsQueue
......
# frozen_string_literal: true
class AutoMergeProcessWorker
class AutoMergeProcessWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :auto_merge
......
# frozen_string_literal: true
class BackgroundMigrationWorker
class BackgroundMigrationWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category_not_owned!
......
# frozen_string_literal: true
class BuildCoverageWorker
class BuildCoverageWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class BuildFinishedWorker
class BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class BuildHooksWorker
class BuildHooksWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class BuildQueueWorker
class BuildQueueWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class BuildSuccessWorker
class BuildSuccessWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class BuildTraceSectionsWorker
class BuildTraceSectionsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
module Chaos
class CpuSpinWorker
class CpuSpinWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ChaosQueue
......
# frozen_string_literal: true
module Chaos
class DbSpinWorker
class DbSpinWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ChaosQueue
......
# frozen_string_literal: true
module Chaos
class KillWorker
class KillWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ChaosQueue
......
# frozen_string_literal: true
module Chaos
class LeakMemWorker
class LeakMemWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ChaosQueue
......
# frozen_string_literal: true
module Chaos
class SleepWorker
class SleepWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ChaosQueue
......
# frozen_string_literal: true
class ChatNotificationWorker
class ChatNotificationWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
TimeoutExceeded = Class.new(StandardError)
......
# frozen_string_literal: true
module Ci
class ArchiveTracesCronWorker
class ArchiveTracesCronWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
......
# frozen_string_literal: true
module Ci
class BuildPrepareWorker
class BuildPrepareWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
module Ci
class BuildScheduleWorker
class BuildScheduleWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
module Ci
class BuildTraceChunkFlushWorker
class BuildTraceChunkFlushWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineBackgroundQueue
......
# frozen_string_literal: true
module Ci
class CreateCrossProjectPipelineWorker
class CreateCrossProjectPipelineWorker # rubocop:disable Scalability/IdempotentWorker
include ::ApplicationWorker
include ::PipelineQueue
......
# frozen_string_literal: true
module Ci
class PipelineBridgeStatusWorker
class PipelineBridgeStatusWorker # rubocop:disable Scalability/IdempotentWorker
include ::ApplicationWorker
include ::PipelineQueue
......
......@@ -2,7 +2,7 @@
module Ci
module ResourceGroups
class AssignResourceFromResourceGroupWorker
class AssignResourceFromResourceGroupWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class CleanupContainerRepositoryWorker
class CleanupContainerRepositoryWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :container_repository
......
# frozen_string_literal: true
class ClusterConfigureIstioWorker
class ClusterConfigureIstioWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
......
# frozen_string_literal: true
class ClusterConfigureWorker
class ClusterConfigureWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
......
# frozen_string_literal: true
class ClusterInstallAppWorker
class ClusterInstallAppWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
# frozen_string_literal: true
class ClusterPatchAppWorker
class ClusterPatchAppWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
# frozen_string_literal: true
class ClusterProjectConfigureWorker
class ClusterProjectConfigureWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
......
# frozen_string_literal: true
class ClusterProvisionWorker
class ClusterProvisionWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
......
# frozen_string_literal: true
class ClusterUpgradeAppWorker
class ClusterUpgradeAppWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
# frozen_string_literal: true
class ClusterWaitForAppInstallationWorker
class ClusterWaitForAppInstallationWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
# frozen_string_literal: true
class ClusterWaitForIngressIpAddressWorker
class ClusterWaitForIngressIpAddressWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
......@@ -2,7 +2,7 @@
module Clusters
module Applications
class ActivateServiceWorker
class ActivateServiceWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
......
......@@ -2,7 +2,7 @@
module Clusters
module Applications
class DeactivateServiceWorker
class DeactivateServiceWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
......
......@@ -2,7 +2,7 @@
module Clusters
module Applications
class UninstallWorker
class UninstallWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
......@@ -2,7 +2,7 @@
module Clusters
module Applications
class WaitForUninstallAppWorker
class WaitForUninstallAppWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
......
......@@ -2,7 +2,7 @@
module Clusters
module Cleanup
class AppWorker
class AppWorker # rubocop:disable Scalability/IdempotentWorker
include ClusterCleanupMethods
def perform(cluster_id, execution_count = 0)
......
......@@ -2,7 +2,7 @@
module Clusters
module Cleanup
class ProjectNamespaceWorker
class ProjectNamespaceWorker # rubocop:disable Scalability/IdempotentWorker
include ClusterCleanupMethods
def perform(cluster_id, execution_count = 0)
......
......@@ -2,7 +2,7 @@
module Clusters
module Cleanup
class ServiceAccountWorker
class ServiceAccountWorker # rubocop:disable Scalability/IdempotentWorker
include ClusterCleanupMethods
def perform(cluster_id)
......
......@@ -89,6 +89,14 @@ module WorkerAttributes
worker_attributes[:resource_boundary] || :unknown
end
def idempotent!
worker_attributes[:idempotent] = true
end
def idempotent?
worker_attributes[:idempotent]
end
def weight(value)
worker_attributes[:weight] = value
end
......
# frozen_string_literal: true
class ContainerExpirationPolicyWorker
class ContainerExpirationPolicyWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include CronjobQueue
......
# frozen_string_literal: true
class CreateCommitSignatureWorker
class CreateCommitSignatureWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :source_code_management
......
# frozen_string_literal: true
class CreateEvidenceWorker
class CreateEvidenceWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :release_governance
......
# frozen_string_literal: true
class CreateNoteDiffFileWorker
class CreateNoteDiffFileWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :source_code_management
......
# frozen_string_literal: true
class CreatePipelineWorker
class CreatePipelineWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class DeleteContainerRepositoryWorker
class DeleteContainerRepositoryWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExclusiveLeaseGuard
......
# frozen_string_literal: true
class DeleteDiffFilesWorker
class DeleteDiffFilesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :source_code_management
......
# frozen_string_literal: true
class DeleteMergedBranchesWorker
class DeleteMergedBranchesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :source_code_management
......
# frozen_string_literal: true
class DeleteStoredFilesWorker
class DeleteStoredFilesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category_not_owned!
......
# frozen_string_literal: true
class DeleteUserWorker
class DeleteUserWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :authentication_and_authorization
......
# frozen_string_literal: true
module Deployments
class FinishedWorker
class FinishedWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :deployment
......
# frozen_string_literal: true
module Deployments
class ForwardDeploymentWorker
class ForwardDeploymentWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :deployment
......
# frozen_string_literal: true
module Deployments
class SuccessWorker
class SuccessWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :deployment
......
# frozen_string_literal: true
class DetectRepositoryLanguagesWorker
class DetectRepositoryLanguagesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
include ExclusiveLeaseGuard
......
# frozen_string_literal: true
class EmailReceiverWorker
class EmailReceiverWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :issue_tracking
......
# frozen_string_literal: true
class EmailsOnPushWorker
class EmailsOnPushWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
attr_reader :email, :skip_premailer
......
# frozen_string_literal: true
module Environments
class AutoStopCronWorker
class AutoStopCronWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
......
......@@ -5,7 +5,7 @@
# If a link to a different GitLab issue exists, a new link
# will still be created, but will not be visible in Sentry
# until the prior link is deleted.
class ErrorTrackingIssueLinkWorker
class ErrorTrackingIssueLinkWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExclusiveLeaseGuard
include Gitlab::Utils::StrongMemoize
......
# frozen_string_literal: true
class ExpireBuildArtifactsWorker
class ExpireBuildArtifactsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
......
# frozen_string_literal: true
class ExpireBuildInstanceArtifactsWorker
class ExpireBuildInstanceArtifactsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :continuous_integration
......
......@@ -6,6 +6,7 @@ class ExpireJobCacheWorker
queue_namespace :pipeline_cache
latency_sensitive_worker!
idempotent!
# rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
......
# frozen_string_literal: true
class ExpirePipelineCacheWorker
class ExpirePipelineCacheWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
......
# frozen_string_literal: true
class FileHookWorker
class FileHookWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
sidekiq_options retry: false
......
# frozen_string_literal: true
class GitGarbageCollectWorker
class GitGarbageCollectWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
sidekiq_options retry: false
......
......@@ -6,7 +6,7 @@ module Gitlab
# number of jobs to complete, without blocking a thread. Once all jobs have
# been completed this worker will advance the import process to the next
# stage.
class AdvanceStageWorker
class AdvanceStageWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
sidekiq_options dead: false
......
......@@ -2,7 +2,7 @@
module Gitlab
module GithubImport
class ImportDiffNoteWorker
class ImportDiffNoteWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
......
......@@ -2,7 +2,7 @@
module Gitlab
module GithubImport
class ImportIssueWorker
class ImportIssueWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
......
......@@ -2,7 +2,7 @@
module Gitlab
module GithubImport
class ImportLfsObjectWorker
class ImportLfsObjectWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
......
......@@ -2,7 +2,7 @@
module Gitlab
module GithubImport
class ImportNoteWorker
class ImportNoteWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
......
......@@ -2,7 +2,7 @@
module Gitlab
module GithubImport
class ImportPullRequestWorker
class ImportPullRequestWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
......
......@@ -2,7 +2,7 @@
module Gitlab
module GithubImport
class RefreshImportJidWorker
class RefreshImportJidWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class FinishImportWorker
class FinishImportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class ImportBaseDataWorker
class ImportBaseDataWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class ImportIssuesAndDiffNotesWorker
class ImportIssuesAndDiffNotesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class ImportLfsObjectsWorker
class ImportLfsObjectsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class ImportNotesWorker
class ImportNotesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class ImportPullRequestsWorker
class ImportPullRequestsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -3,7 +3,7 @@
module Gitlab
module GithubImport
module Stage
class ImportRepositoryWorker
class ImportRepositoryWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include GithubImport::Queue
include StageMethods
......
......@@ -18,7 +18,7 @@
# - It marks the import as finished when all remaining jobs are done
module Gitlab
module PhabricatorImport
class BaseWorker
class BaseWorker # rubocop:disable Scalability/IdempotentWorker
include WorkerAttributes
include Gitlab::ExclusiveLeaseHelpers
......
# frozen_string_literal: true
module Gitlab
module PhabricatorImport
class ImportTasksWorker < BaseWorker
class ImportTasksWorker < BaseWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ProjectImportOptions # This marks the project as failed after too many tries
......
# frozen_string_literal: true
class GitlabShellWorker
class GitlabShellWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include Gitlab::ShellAdapter
......
# frozen_string_literal: true
class GitlabUsagePingWorker
class GitlabUsagePingWorker # rubocop:disable Scalability/IdempotentWorker
LEASE_TIMEOUT = 86400
include ApplicationWorker
......
# frozen_string_literal: true
class GroupDestroyWorker
class GroupDestroyWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
......
# frozen_string_literal: true
class GroupExportWorker
class GroupExportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
......
# frozen_string_literal: true
class GroupImportWorker
class GroupImportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
......
# frozen_string_literal: true
module HashedStorage
class BaseWorker
class BaseWorker # rubocop:disable Scalability/IdempotentWorker
include ExclusiveLeaseGuard
include WorkerAttributes
......
# frozen_string_literal: true
module HashedStorage
class MigratorWorker
class MigratorWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :hashed_storage
......
# frozen_string_literal: true
module HashedStorage
class ProjectMigrateWorker < BaseWorker
class ProjectMigrateWorker < BaseWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :hashed_storage
......
# frozen_string_literal: true
module HashedStorage
class ProjectRollbackWorker < BaseWorker
class ProjectRollbackWorker < BaseWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :hashed_storage
......
# frozen_string_literal: true
module HashedStorage
class RollbackerWorker
class RollbackerWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :hashed_storage
......
# frozen_string_literal: true
class ImportExportProjectCleanupWorker
class ImportExportProjectCleanupWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
......
# frozen_string_literal: true
class ImportIssuesCsvWorker
class ImportIssuesCsvWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :issue_tracking
......
# frozen_string_literal: true
module IncidentManagement
class ProcessAlertWorker
class ProcessAlertWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :incident_management
......
# frozen_string_literal: true
class InvalidGpgSignatureUpdateWorker
class InvalidGpgSignatureUpdateWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :source_code_management
......
......@@ -3,7 +3,7 @@
require 'json'
require 'socket'
class IrkerWorker
class IrkerWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :integrations
......
# frozen_string_literal: true
class IssueDueSchedulerWorker
class IssueDueSchedulerWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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