Merge "Trove external dependency management"
This commit is contained in:
commit
35737af627
@ -194,15 +194,12 @@ class trove::api(
|
|||||||
$auth_protocol = 'http',
|
$auth_protocol = 'http',
|
||||||
) inherits trove {
|
) inherits trove {
|
||||||
|
|
||||||
require ::keystone::python
|
include ::trove::deps
|
||||||
include ::trove::db
|
include ::trove::db
|
||||||
|
include ::trove::db::sync
|
||||||
include ::trove::logging
|
include ::trove::logging
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
|
|
||||||
Trove_config<||> ~> Exec['post-trove_config']
|
|
||||||
Trove_config<||> ~> Service['trove-api']
|
|
||||||
Trove_api_paste_ini<||> ~> Service['trove-api']
|
|
||||||
|
|
||||||
# basic service config
|
# basic service config
|
||||||
trove_config {
|
trove_config {
|
||||||
'DEFAULT/bind_host': value => $bind_host;
|
'DEFAULT/bind_host': value => $bind_host;
|
||||||
|
@ -21,20 +21,26 @@
|
|||||||
#
|
#
|
||||||
# === Parameters:
|
# === Parameters:
|
||||||
#
|
#
|
||||||
|
# [*client_package_name*]
|
||||||
|
# (optional) The name of python trove client package
|
||||||
|
# Defaults to $trove::params::client_package_name
|
||||||
|
#
|
||||||
# [*package_ensure*]
|
# [*package_ensure*]
|
||||||
# (optional) The state of the package
|
# (optional) The state of the package
|
||||||
# Defaults to present
|
# Defaults to present
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
class trove::client (
|
class trove::client (
|
||||||
$package_ensure = present
|
$client_package_name = $trove::params::client_package_name,
|
||||||
) {
|
$package_ensure = present,
|
||||||
|
) inherits trove::params {
|
||||||
|
|
||||||
include ::trove::params
|
include ::trove::deps
|
||||||
|
|
||||||
package { 'python-troveclient':
|
package { 'python-troveclient':
|
||||||
ensure => $package_ensure,
|
ensure => $package_ensure,
|
||||||
name => $::trove::params::client_package_name,
|
name => $client_package_name,
|
||||||
|
tag => 'openstack',
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,11 +64,9 @@ class trove::conductor(
|
|||||||
$conductor_manager = 'trove.conductor.manager.Manager',
|
$conductor_manager = 'trove.conductor.manager.Manager',
|
||||||
) inherits trove {
|
) inherits trove {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
|
|
||||||
Trove_conductor_config<||> ~> Exec['post-trove_config']
|
|
||||||
Trove_conductor_config<||> ~> Service['trove-conductor']
|
|
||||||
|
|
||||||
if $::trove::database_connection {
|
if $::trove::database_connection {
|
||||||
if($::trove::database_connection =~ /mysql:\/\/\S+:\S+@\S+\/\S+/) {
|
if($::trove::database_connection =~ /mysql:\/\/\S+:\S+@\S+\/\S+/) {
|
||||||
require 'mysql::bindings'
|
require 'mysql::bindings'
|
||||||
|
@ -69,6 +69,8 @@ class trove::config (
|
|||||||
$trove_api_paste_ini = {},
|
$trove_api_paste_ini = {},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
|
|
||||||
validate_hash($trove_config)
|
validate_hash($trove_config)
|
||||||
validate_hash($trove_taskmanager_config)
|
validate_hash($trove_taskmanager_config)
|
||||||
validate_hash($trove_conductor_config)
|
validate_hash($trove_conductor_config)
|
||||||
|
@ -43,6 +43,7 @@ class trove::db (
|
|||||||
$database_max_overflow = 20,
|
$database_max_overflow = 20,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
|
|
||||||
# NOTE(spredzy): In order to keep backward compatibility we rely on the pick function
|
# NOTE(spredzy): In order to keep backward compatibility we rely on the pick function
|
||||||
@ -85,7 +86,7 @@ class trove::db (
|
|||||||
package {'trove-backend-package':
|
package {'trove-backend-package':
|
||||||
ensure => present,
|
ensure => present,
|
||||||
name => $backend_package,
|
name => $backend_package,
|
||||||
tag => 'openstack',
|
tag => ['openstack', 'trove-package'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ class trove::db::mysql(
|
|||||||
$collate = 'utf8_general_ci',
|
$collate = 'utf8_general_ci',
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
|
|
||||||
validate_string($password)
|
validate_string($password)
|
||||||
|
|
||||||
::openstacklib::db::mysql { 'trove':
|
::openstacklib::db::mysql { 'trove':
|
||||||
@ -71,5 +73,7 @@ class trove::db::mysql(
|
|||||||
allowed_hosts => $allowed_hosts,
|
allowed_hosts => $allowed_hosts,
|
||||||
}
|
}
|
||||||
|
|
||||||
::Openstacklib::Db::Mysql['trove'] ~> Exec<| title == 'trove-manage db_sync' |>
|
Anchor['trove::db::begin']
|
||||||
|
~> Class['trove::db::mysql']
|
||||||
|
~> Anchor['trove::db::end']
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,9 @@ class trove::db::postgresql(
|
|||||||
$privileges = 'ALL',
|
$privileges = 'ALL',
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Class['trove::db::postgresql'] -> Service<| title == 'trove' |>
|
include ::trove::deps
|
||||||
|
|
||||||
|
validate_string($password)
|
||||||
|
|
||||||
::openstacklib::db::postgresql { 'trove':
|
::openstacklib::db::postgresql { 'trove':
|
||||||
password_hash => postgresql_password($user, $password),
|
password_hash => postgresql_password($user, $password),
|
||||||
@ -42,6 +44,7 @@ class trove::db::postgresql(
|
|||||||
privileges => $privileges,
|
privileges => $privileges,
|
||||||
}
|
}
|
||||||
|
|
||||||
::Openstacklib::Db::Postgresql['trove'] ~> Exec<| title == 'trove-manage db_sync' |>
|
Anchor['trove::db::begin']
|
||||||
|
~> Class['trove::db::postgresql']
|
||||||
|
~> Anchor['trove::db::end']
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,18 @@
|
|||||||
# Class to execute "trove-manage db_sync
|
# Class to execute "trove-manage db_sync
|
||||||
#
|
#
|
||||||
class trove::db::sync {
|
class trove::db::sync {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
|
|
||||||
exec { 'trove-manage db_sync':
|
exec { 'trove-manage db_sync':
|
||||||
path => '/usr/bin',
|
path => '/usr/bin',
|
||||||
user => 'trove',
|
user => 'trove',
|
||||||
refreshonly => true,
|
refreshonly => true,
|
||||||
subscribe => Trove_config['database/connection'],
|
subscribe => [
|
||||||
|
Anchor['trove::install::end'],
|
||||||
|
Anchor['trove::config::end'],
|
||||||
|
Anchor['trove::dbsync::begin']
|
||||||
|
],
|
||||||
|
notify => Anchor['trove::dbsync::end'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
manifests/deps.pp
Normal file
65
manifests/deps.pp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# == Class: trove::deps
|
||||||
|
#
|
||||||
|
# trove anchors and dependency management
|
||||||
|
#
|
||||||
|
class trove::deps {
|
||||||
|
# Setup anchors for install, config and service phases of the module. These
|
||||||
|
# anchors allow external modules to hook the begin and end of any of these
|
||||||
|
# phases. Package or service management can also be replaced by ensuring the
|
||||||
|
# package is absent or turning off service management and having the
|
||||||
|
# replacement depend on the appropriate anchors. When applicable, end tags
|
||||||
|
# should be notified so that subscribers can determine if installation,
|
||||||
|
# config or service state changed and act on that if needed.
|
||||||
|
anchor { 'trove::install::begin': }
|
||||||
|
-> Package<| tag == 'trove-package'|>
|
||||||
|
~> anchor { 'trove::install::end': }
|
||||||
|
-> anchor { 'trove::config::begin': }
|
||||||
|
-> Trove_config<||>
|
||||||
|
~> anchor { 'trove::config::end': }
|
||||||
|
-> anchor { 'trove::db::begin': }
|
||||||
|
-> anchor { 'trove::db::end': }
|
||||||
|
~> anchor { 'trove::dbsync::begin': }
|
||||||
|
-> anchor { 'trove::dbsync::end': }
|
||||||
|
~> anchor { 'trove::service::begin': }
|
||||||
|
~> Service<| tag == 'trove-service' |>
|
||||||
|
~> anchor { 'trove::service::end': }
|
||||||
|
|
||||||
|
# Include all the other trove config pieces in the config block.
|
||||||
|
# Don't put them above because there's no chain between each individual part
|
||||||
|
# of the config.
|
||||||
|
Anchor['trove::config::begin']
|
||||||
|
-> Trove_taskmanager_config<||>
|
||||||
|
~> Anchor['trove::config::end']
|
||||||
|
Anchor['trove::config::begin']
|
||||||
|
-> Trove_conductor_config<||>
|
||||||
|
~> Anchor['trove::config::end']
|
||||||
|
Anchor['trove::config::begin']
|
||||||
|
-> Trove_guestagent_config<||>
|
||||||
|
~> Anchor['trove::config::end']
|
||||||
|
|
||||||
|
# Also include paste ini config in the config section
|
||||||
|
Anchor['trove::config::begin']
|
||||||
|
-> Trove_api_paste_ini<||>
|
||||||
|
~> Anchor['trove::config::end']
|
||||||
|
|
||||||
|
# policy config should occur in the config block also as soon as
|
||||||
|
# puppet-trove supports it. Leave commented out for now.
|
||||||
|
# Anchor['trove::config::begin']
|
||||||
|
# -> Openstacklib::Policy::Base<||>
|
||||||
|
# ~> Anchor['trove::config::end']
|
||||||
|
|
||||||
|
# We need troveclient installed before marking service end so that trove
|
||||||
|
# will have clients available to create resources. This tag handles the
|
||||||
|
# troveclient but indirectly since the client is not available in
|
||||||
|
# all catalogs that don't need the client class (like many spec tests).
|
||||||
|
# Once the troveclient is installed we will setup the datastores and
|
||||||
|
# datastore_versions. Datastore_versions must come after datastores.
|
||||||
|
Package<| tag == 'openstack'|>
|
||||||
|
~> Anchor['trove::service::end']
|
||||||
|
-> Trove_datastore<||>
|
||||||
|
-> Trove_datastore_version<||>
|
||||||
|
|
||||||
|
# Installation or config changes will always restart services.
|
||||||
|
Anchor['trove::install::end'] ~> Anchor['trove::service::begin']
|
||||||
|
Anchor['trove::config::end'] ~> Anchor['trove::service::begin']
|
||||||
|
}
|
@ -55,19 +55,16 @@ define trove::generic_service(
|
|||||||
$ensure_package = 'present'
|
$ensure_package = 'present'
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
include ::trove::db::sync
|
|
||||||
|
|
||||||
$trove_title = "trove-${name}"
|
$trove_title = "trove-${name}"
|
||||||
Exec['post-trove_config'] ~> Service<| title == $trove_title |>
|
|
||||||
Exec<| title == 'trove-manage db_sync' |> ~> Service<| title == $trove_title |>
|
|
||||||
|
|
||||||
if ($package_name) {
|
if ($package_name) {
|
||||||
if !defined(Package[$package_name]) {
|
if !defined(Package[$package_name]) {
|
||||||
package { $trove_title:
|
package { $trove_title:
|
||||||
ensure => $ensure_package,
|
ensure => $ensure_package,
|
||||||
name => $package_name,
|
name => $package_name,
|
||||||
notify => Service[$trove_title],
|
|
||||||
tag => ['openstack', 'trove-package'],
|
tag => ['openstack', 'trove-package'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,11 +69,9 @@ class trove::guestagent(
|
|||||||
$control_exchange = 'trove'
|
$control_exchange = 'trove'
|
||||||
) inherits trove {
|
) inherits trove {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
|
|
||||||
Trove_guestagent_config<||> ~> Exec['post-trove_config']
|
|
||||||
Trove_guestagent_config<||> ~> Service['trove-guestagent']
|
|
||||||
|
|
||||||
# basic service config
|
# basic service config
|
||||||
trove_guestagent_config {
|
trove_guestagent_config {
|
||||||
'DEFAULT/verbose': value => $verbose;
|
'DEFAULT/verbose': value => $verbose;
|
||||||
@ -92,7 +90,7 @@ class trove::guestagent(
|
|||||||
trove_guestagent_config { 'DEFAULT/os_region_name': value => $::trove::os_region_name }
|
trove_guestagent_config { 'DEFAULT/os_region_name': value => $::trove::os_region_name }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
trove_guestagent_config {'DEFAULT/os_region_name': ensure => absent }
|
trove_guestagent_config { 'DEFAULT/os_region_name': ensure => absent }
|
||||||
}
|
}
|
||||||
|
|
||||||
trove_guestagent_config {
|
trove_guestagent_config {
|
||||||
|
@ -285,15 +285,9 @@ class trove(
|
|||||||
$qpid_protocol = undef,
|
$qpid_protocol = undef,
|
||||||
$qpid_tcp_nodelay = undef,
|
$qpid_tcp_nodelay = undef,
|
||||||
) {
|
) {
|
||||||
|
include ::trove::deps
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
|
|
||||||
exec { 'post-trove_config':
|
|
||||||
command => '/bin/echo "Trove config has changed"',
|
|
||||||
refreshonly => true,
|
|
||||||
}
|
|
||||||
|
|
||||||
Trove_datastore<||> -> Trove_datastore_version<||>
|
|
||||||
|
|
||||||
if $nova_compute_url {
|
if $nova_compute_url {
|
||||||
trove_config { 'DEFAULT/nova_compute_url': value => $nova_compute_url }
|
trove_config { 'DEFAULT/nova_compute_url': value => $nova_compute_url }
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,8 @@ class trove::keystone::auth (
|
|||||||
$admin_address = undef,
|
$admin_address = undef,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
|
|
||||||
if $port {
|
if $port {
|
||||||
warning('The port parameter is deprecated, use public_url, internal_url and admin_url instead.')
|
warning('The port parameter is deprecated, use public_url, internal_url and admin_url instead.')
|
||||||
}
|
}
|
||||||
@ -203,7 +205,7 @@ class trove::keystone::auth (
|
|||||||
Keystone_user_role["${auth_name}@${tenant}"] ~> Service <| tag == 'trove-server' |>
|
Keystone_user_role["${auth_name}@${tenant}"] ~> Service <| tag == 'trove-server' |>
|
||||||
|
|
||||||
Keystone_endpoint<| title == "${region}/${real_service_name}::${service_type}" |>
|
Keystone_endpoint<| title == "${region}/${real_service_name}::${service_type}" |>
|
||||||
~> Service <| tag == 'trove-server' |>
|
~> Service <| tag == 'trove-server' |>
|
||||||
|
|
||||||
keystone::resource::service_identity { 'trove':
|
keystone::resource::service_identity { 'trove':
|
||||||
configure_user => true,
|
configure_user => true,
|
||||||
|
@ -116,6 +116,8 @@ class trove::logging(
|
|||||||
$log_date_format = $::os_service_default,
|
$log_date_format = $::os_service_default,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
|
|
||||||
# NOTE(spredzy): In order to keep backward compatibility we rely on the pick function
|
# NOTE(spredzy): In order to keep backward compatibility we rely on the pick function
|
||||||
# to use trove::<myparam> first then trove::logging::<myparam>.
|
# to use trove::<myparam> first then trove::logging::<myparam>.
|
||||||
$use_syslog_real = pick($::trove::api::use_syslog, $use_syslog)
|
$use_syslog_real = pick($::trove::api::use_syslog, $use_syslog)
|
||||||
@ -153,4 +155,3 @@ class trove::logging(
|
|||||||
'DEFAULT/log_date_format' : value => $log_date_format;
|
'DEFAULT/log_date_format' : value => $log_date_format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ class trove::quota (
|
|||||||
$quota_driver = 'trove.quota.quota.DbQuotaDriver',
|
$quota_driver = 'trove.quota.quota.DbQuotaDriver',
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
|
|
||||||
trove_config {
|
trove_config {
|
||||||
'DEFAULT/max_instances_per_user': value => $max_instances_per_user;
|
'DEFAULT/max_instances_per_user': value => $max_instances_per_user;
|
||||||
'DEFAULT/max_accepted_volume_size': value => $max_accepted_volume_size;
|
'DEFAULT/max_accepted_volume_size': value => $max_accepted_volume_size;
|
||||||
|
@ -101,12 +101,9 @@ class trove::taskmanager(
|
|||||||
$taskmanager_queue = 'taskmanager',
|
$taskmanager_queue = 'taskmanager',
|
||||||
) inherits trove {
|
) inherits trove {
|
||||||
|
|
||||||
|
include ::trove::deps
|
||||||
include ::trove::params
|
include ::trove::params
|
||||||
|
|
||||||
Package[$::trove::params::taskmanager_package_name] -> Trove_taskmanager_config<||>
|
|
||||||
Trove_taskmanager_config<||> ~> Exec['post-trove_config']
|
|
||||||
Trove_taskmanager_config<||> ~> Service['trove-taskmanager']
|
|
||||||
|
|
||||||
if $::trove::database_connection {
|
if $::trove::database_connection {
|
||||||
if($::trove::database_connection =~ /mysql:\/\/\S+:\S+@\S+\/\S+/) {
|
if($::trove::database_connection =~ /mysql:\/\/\S+:\S+@\S+\/\S+/) {
|
||||||
require 'mysql::bindings'
|
require 'mysql::bindings'
|
||||||
@ -303,7 +300,7 @@ class trove::taskmanager(
|
|||||||
if $use_guestagent_template {
|
if $use_guestagent_template {
|
||||||
file { $guestagent_config_file:
|
file { $guestagent_config_file:
|
||||||
content => template('trove/trove-guestagent.conf.erb'),
|
content => template('trove/trove-guestagent.conf.erb'),
|
||||||
require => Package[$::trove::params::taskmanager_package_name]
|
require => Anchor['trove::install::end'],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
class {'::trove::guestagent':
|
class {'::trove::guestagent':
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Dependencies are now managed in an external class.
|
||||||
|
This allows installing Trove via other methods besides
|
||||||
|
packages.
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- The client_package_name is now configurable.
|
||||||
|
It defaults to the name in the params class.
|
@ -55,7 +55,7 @@ describe 'trove::api' do
|
|||||||
is_expected.to contain_package('trove-api').with(
|
is_expected.to contain_package('trove-api').with(
|
||||||
:name => platform_params[:api_package_name],
|
:name => platform_params[:api_package_name],
|
||||||
:ensure => 'present',
|
:ensure => 'present',
|
||||||
:notify => 'Service[trove-api]'
|
:tag => ['openstack', 'trove-package'],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ describe 'trove::conductor' do
|
|||||||
is_expected.to contain_package('trove-conductor').with(
|
is_expected.to contain_package('trove-conductor').with(
|
||||||
:name => platform_params[:conductor_package_name],
|
:name => platform_params[:conductor_package_name],
|
||||||
:ensure => 'present',
|
:ensure => 'present',
|
||||||
:notify => 'Service[trove-conductor]'
|
:tag => ['openstack', 'trove-package'],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ describe 'trove::db' do
|
|||||||
is_expected.to contain_package('trove-backend-package').with(
|
is_expected.to contain_package('trove-backend-package').with(
|
||||||
:ensure => 'present',
|
:ensure => 'present',
|
||||||
:name => 'python-pymysql',
|
:name => 'python-pymysql',
|
||||||
:tag => 'openstack'
|
:tag => ['openstack', 'trove-package'],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
17
spec/classes/trove_deps_spec.rb
Normal file
17
spec/classes/trove_deps_spec.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe 'trove::deps' do
|
||||||
|
|
||||||
|
it 'set up the anchors' do
|
||||||
|
is_expected.to contain_anchor('trove::install::begin')
|
||||||
|
is_expected.to contain_anchor('trove::install::end')
|
||||||
|
is_expected.to contain_anchor('trove::config::begin')
|
||||||
|
is_expected.to contain_anchor('trove::config::end')
|
||||||
|
is_expected.to contain_anchor('trove::db::begin')
|
||||||
|
is_expected.to contain_anchor('trove::db::end')
|
||||||
|
is_expected.to contain_anchor('trove::dbsync::begin')
|
||||||
|
is_expected.to contain_anchor('trove::dbsync::end')
|
||||||
|
is_expected.to contain_anchor('trove::service::begin')
|
||||||
|
is_expected.to contain_anchor('trove::service::end')
|
||||||
|
end
|
||||||
|
end
|
@ -22,7 +22,7 @@ describe 'trove::guestagent' do
|
|||||||
is_expected.to contain_package('trove-guestagent').with(
|
is_expected.to contain_package('trove-guestagent').with(
|
||||||
:name => platform_params[:guestagent_package_name],
|
:name => platform_params[:guestagent_package_name],
|
||||||
:ensure => 'present',
|
:ensure => 'present',
|
||||||
:notify => 'Service[trove-guestagent]'
|
:tag => ['openstack', 'trove-package']
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ describe 'trove::taskmanager' do
|
|||||||
is_expected.to contain_package('trove-taskmanager').with(
|
is_expected.to contain_package('trove-taskmanager').with(
|
||||||
:name => platform_params[:taskmanager_package_name],
|
:name => platform_params[:taskmanager_package_name],
|
||||||
:ensure => 'present',
|
:ensure => 'present',
|
||||||
:notify => 'Service[trove-taskmanager]'
|
:tag => ['openstack', 'trove-package']
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user