Heat support
Fixes: rhbz#967309 Change-Id: I8d04588719e56bbb6fa9c3b2be06da34c3d0d65b
This commit is contained in:
parent
c9504604ba
commit
1523dc39a1
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -82,3 +82,6 @@
|
|||||||
[submodule "packstack/puppet/modules/mongodb"]
|
[submodule "packstack/puppet/modules/mongodb"]
|
||||||
path = packstack/puppet/modules/mongodb
|
path = packstack/puppet/modules/mongodb
|
||||||
url = https://github.com/puppetlabs/puppetlabs-mongodb.git
|
url = https://github.com/puppetlabs/puppetlabs-mongodb.git
|
||||||
|
[submodule "packstack/puppet/modules/heat"]
|
||||||
|
path = packstack/puppet/modules/heat
|
||||||
|
url = https://github.com/packstack/puppet-heat.git
|
||||||
|
202
packstack/plugins/heat_750.py
Normal file
202
packstack/plugins/heat_750.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
Installs and configures heat
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from packstack.installer import utils
|
||||||
|
from packstack.installer import validators
|
||||||
|
|
||||||
|
from packstack.modules.ospluginutils import (getManifestTemplate,
|
||||||
|
manifestfiles,
|
||||||
|
appendManifestFile)
|
||||||
|
|
||||||
|
controller = None
|
||||||
|
|
||||||
|
# Plugin name
|
||||||
|
PLUGIN_NAME = "OS-HEAT"
|
||||||
|
PLUGIN_NAME_COLORED = utils.color_text(PLUGIN_NAME, 'blue')
|
||||||
|
|
||||||
|
logging.debug("plugin %s loaded", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def initConfig(controllerObject):
|
||||||
|
global controller
|
||||||
|
controller = controllerObject
|
||||||
|
logging.debug("Adding OpenStack Heat configuration")
|
||||||
|
parameters = [
|
||||||
|
{"CMD_OPTION" : "heat-host",
|
||||||
|
"USAGE" : ('The IP address of the server on which '
|
||||||
|
'to install Heat service'),
|
||||||
|
"PROMPT" : 'Enter the IP address of the Heat service',
|
||||||
|
"OPTION_LIST" : [],
|
||||||
|
"VALIDATORS" : [validators.validate_ssh],
|
||||||
|
"DEFAULT_VALUE" : utils.get_localhost_ip(),
|
||||||
|
"MASK_INPUT" : False,
|
||||||
|
"LOOSE_VALIDATION": True,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_HOST",
|
||||||
|
"USE_DEFAULT" : False,
|
||||||
|
"NEED_CONFIRM" : False,
|
||||||
|
"CONDITION" : False },
|
||||||
|
|
||||||
|
{"CMD_OPTION" : "heat-mysql-password",
|
||||||
|
"USAGE" : 'The password used by Heat user to authenticate against MySQL',
|
||||||
|
"PROMPT" : "Enter the password for the Heat MySQL user",
|
||||||
|
"OPTION_LIST" : [],
|
||||||
|
"VALIDATORS" : [validators.validate_not_empty],
|
||||||
|
"DEFAULT_VALUE" : uuid.uuid4().hex[:16],
|
||||||
|
"MASK_INPUT" : True,
|
||||||
|
"LOOSE_VALIDATION": False,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_DB_PW",
|
||||||
|
"USE_DEFAULT" : True,
|
||||||
|
"NEED_CONFIRM" : True,
|
||||||
|
"CONDITION" : False },
|
||||||
|
|
||||||
|
{"CMD_OPTION" : "heat-ks-passwd",
|
||||||
|
"USAGE" : "The password to use for the Heat to authenticate with Keystone",
|
||||||
|
"PROMPT" : "Enter the password for the Heat Keystone access",
|
||||||
|
"OPTION_LIST" : [],
|
||||||
|
"VALIDATORS" : [validators.validate_not_empty],
|
||||||
|
"DEFAULT_VALUE" : uuid.uuid4().hex[:16],
|
||||||
|
"MASK_INPUT" : True,
|
||||||
|
"LOOSE_VALIDATION": False,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_KS_PW",
|
||||||
|
"USE_DEFAULT" : True,
|
||||||
|
"NEED_CONFIRM" : True,
|
||||||
|
"CONDITION" : False },
|
||||||
|
|
||||||
|
{"CMD_OPTION" : "os-heat-cloudwatch-install",
|
||||||
|
"USAGE" : ("Set to 'y' if you would like Packstack to "
|
||||||
|
"install Heat CloudWatch API"),
|
||||||
|
"PROMPT" : "Should Packstack install Heat CloudWatch API",
|
||||||
|
"OPTION_LIST" : ["y", "n"],
|
||||||
|
"VALIDATORS" : [validators.validate_options],
|
||||||
|
"DEFAULT_VALUE" : "n",
|
||||||
|
"MASK_INPUT" : False,
|
||||||
|
"LOOSE_VALIDATION": False,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_CLOUDWATCH_INSTALL",
|
||||||
|
"USE_DEFAULT" : False,
|
||||||
|
"NEED_CONFIRM" : False,
|
||||||
|
"CONDITION" : False },
|
||||||
|
|
||||||
|
{"CMD_OPTION" : "os-heat-cfn-install",
|
||||||
|
"USAGE" : ("Set to 'y' if you would like Packstack to "
|
||||||
|
"install Heat CloudFormation API"),
|
||||||
|
"PROMPT" : "Should Packstack install Heat CloudFormation API",
|
||||||
|
"OPTION_LIST" : ["y", "n"],
|
||||||
|
"VALIDATORS" : [validators.validate_options],
|
||||||
|
"DEFAULT_VALUE" : "n",
|
||||||
|
"MASK_INPUT" : False,
|
||||||
|
"LOOSE_VALIDATION": False,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_CFN_INSTALL",
|
||||||
|
"USE_DEFAULT" : False,
|
||||||
|
"NEED_CONFIRM" : False,
|
||||||
|
"CONDITION" : False },
|
||||||
|
]
|
||||||
|
group = {"GROUP_NAME" : "Heat",
|
||||||
|
"DESCRIPTION" : "Heat Config parameters",
|
||||||
|
"PRE_CONDITION" : "CONFIG_HEAT_INSTALL",
|
||||||
|
"PRE_CONDITION_MATCH" : "y",
|
||||||
|
"POST_CONDITION" : False,
|
||||||
|
"POST_CONDITION_MATCH": True}
|
||||||
|
controller.addGroup(group, parameters)
|
||||||
|
|
||||||
|
parameters = [
|
||||||
|
{"CMD_OPTION" : "heat-api-cloudwatch-host",
|
||||||
|
"USAGE" : ('The IP address of the server on which '
|
||||||
|
'to install Heat CloudWatch API service'),
|
||||||
|
"PROMPT" : ('Enter the IP address of the Heat CloudWatch API '
|
||||||
|
'server'),
|
||||||
|
"OPTION_LIST" : [],
|
||||||
|
"VALIDATORS" : [validators.validate_ssh],
|
||||||
|
"DEFAULT_VALUE" : utils.get_localhost_ip(),
|
||||||
|
"MASK_INPUT" : False,
|
||||||
|
"LOOSE_VALIDATION": True,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_CLOUDWATCH_HOST",
|
||||||
|
"USE_DEFAULT" : False,
|
||||||
|
"NEED_CONFIRM" : False,
|
||||||
|
"CONDITION" : False },
|
||||||
|
]
|
||||||
|
group = {"GROUP_NAME" : "Heat CloudWatch API",
|
||||||
|
"DESCRIPTION" : "Heat CloudWatch API config parameters",
|
||||||
|
"PRE_CONDITION" : "CONFIG_HEAT_CLOUDWATCH_INSTALL",
|
||||||
|
"PRE_CONDITION_MATCH" : "y",
|
||||||
|
"POST_CONDITION" : False,
|
||||||
|
"POST_CONDITION_MATCH": True}
|
||||||
|
controller.addGroup(group, parameters)
|
||||||
|
|
||||||
|
parameters = [
|
||||||
|
{"CMD_OPTION" : "heat-api-cfn-host",
|
||||||
|
"USAGE" : ('The IP address of the server on which '
|
||||||
|
'to install Heat CloudFormation API service'),
|
||||||
|
"PROMPT" : ('Enter the IP address of the Heat CloudFormation '
|
||||||
|
'API server'),
|
||||||
|
"OPTION_LIST" : [],
|
||||||
|
"VALIDATORS" : [validators.validate_ssh],
|
||||||
|
"DEFAULT_VALUE" : utils.get_localhost_ip(),
|
||||||
|
"MASK_INPUT" : False,
|
||||||
|
"LOOSE_VALIDATION": True,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_CFN_HOST",
|
||||||
|
"USE_DEFAULT" : False,
|
||||||
|
"NEED_CONFIRM" : False,
|
||||||
|
"CONDITION" : False },
|
||||||
|
]
|
||||||
|
group = {"GROUP_NAME" : "Heat CloudFormation API",
|
||||||
|
"DESCRIPTION" : "Heat CloudFormation API config parameters",
|
||||||
|
"PRE_CONDITION" : "CONFIG_HEAT_CFN_INSTALL",
|
||||||
|
"PRE_CONDITION_MATCH" : "y",
|
||||||
|
"POST_CONDITION" : False,
|
||||||
|
"POST_CONDITION_MATCH": True}
|
||||||
|
controller.addGroup(group, parameters)
|
||||||
|
|
||||||
|
|
||||||
|
def initSequences(controller):
|
||||||
|
if controller.CONF['CONFIG_HEAT_INSTALL'] != 'y':
|
||||||
|
return
|
||||||
|
steps = [{'title': 'Adding Heat manifest entries',
|
||||||
|
'functions': [create_manifest]},
|
||||||
|
{'title': 'Adding Heat Keystone manifest entries',
|
||||||
|
'functions':[create_keystone_manifest]}]
|
||||||
|
|
||||||
|
if controller.CONF.get('CONFIG_HEAT_CLOUDWATCH_INSTALL', 'n') == 'y':
|
||||||
|
steps.append({'title': 'Adding Heat CloudWatch API manifest entries',
|
||||||
|
'functions': [create_cloudwatch_manifest]})
|
||||||
|
if controller.CONF.get('CONFIG_HEAT_CFN_INSTALL', 'n') == 'y':
|
||||||
|
steps.append({'title': 'Adding Heat CloudFormation API manifest entries',
|
||||||
|
'functions': [create_cfn_manifest]})
|
||||||
|
controller.addSequence("Installing Heat", [], [], steps)
|
||||||
|
|
||||||
|
|
||||||
|
def create_manifest(config):
|
||||||
|
if config['CONFIG_HEAT_CLOUDWATCH_INSTALL'] == 'y':
|
||||||
|
config['CONFIG_HEAT_WATCH_HOST'] = config['CONFIG_HEAT_CLOUDWATCH_HOST']
|
||||||
|
else:
|
||||||
|
config['CONFIG_HEAT_WATCH_HOST'] = config['CONFIG_HEAT_HOST']
|
||||||
|
if config['CONFIG_HEAT_CFN_INSTALL'] == 'y':
|
||||||
|
config['CONFIG_HEAT_METADATA_HOST'] = config['CONFIG_HEAT_CFN_HOST']
|
||||||
|
else:
|
||||||
|
config['CONFIG_HEAT_METADATA_HOST'] = config['CONFIG_HEAT_HOST']
|
||||||
|
|
||||||
|
manifestfile = "%s_heat.pp" % controller.CONF['CONFIG_HEAT_HOST']
|
||||||
|
manifestdata = getManifestTemplate("heat.pp")
|
||||||
|
appendManifestFile(manifestfile, manifestdata)
|
||||||
|
|
||||||
|
|
||||||
|
def create_keystone_manifest(config):
|
||||||
|
manifestfile = "%s_keystone.pp" % controller.CONF['CONFIG_KEYSTONE_HOST']
|
||||||
|
manifestdata = getManifestTemplate("keystone_heat.pp")
|
||||||
|
appendManifestFile(manifestfile, manifestdata)
|
||||||
|
|
||||||
|
|
||||||
|
def create_cloudwatch_manifest(config):
|
||||||
|
manifestfile = "%s_heatcw.pp" % controller.CONF['CONFIG_HEAT_CLOUDWATCH_HOST']
|
||||||
|
manifestdata = getManifestTemplate("heat_cloudwatch.pp")
|
||||||
|
appendManifestFile(manifestfile, manifestdata, marker='heat')
|
||||||
|
|
||||||
|
|
||||||
|
def create_cfn_manifest(config):
|
||||||
|
manifestfile = "%s_heatcnf.pp" % controller.CONF['CONFIG_HEAT_CFN_HOST']
|
||||||
|
manifestdata = getManifestTemplate("heat_cfn.pp")
|
||||||
|
appendManifestFile(manifestfile, manifestdata, marker='heat')
|
@ -103,7 +103,7 @@ def createmanifest(config):
|
|||||||
manifestdata.append(getManifestTemplate(template))
|
manifestdata.append(getManifestTemplate(template))
|
||||||
|
|
||||||
append_for("keystone", suffix)
|
append_for("keystone", suffix)
|
||||||
for mod in ['nova', 'cinder', 'glance', 'neutron']:
|
for mod in ['nova', 'cinder', 'glance', 'neutron', 'heat']:
|
||||||
if config['CONFIG_%s_INSTALL' % mod.upper()] == 'y':
|
if config['CONFIG_%s_INSTALL' % mod.upper()] == 'y':
|
||||||
append_for(mod, suffix)
|
append_for(mod, suffix)
|
||||||
|
|
||||||
|
@ -119,6 +119,18 @@ def initConfig(controllerObject):
|
|||||||
"USE_DEFAULT" : False,
|
"USE_DEFAULT" : False,
|
||||||
"NEED_CONFIRM" : False,
|
"NEED_CONFIRM" : False,
|
||||||
"CONDITION" : False },
|
"CONDITION" : False },
|
||||||
|
{"CMD_OPTION" : "os-heat-install",
|
||||||
|
"USAGE" : "Set to 'y' if you would like Packstack to install Heat",
|
||||||
|
"PROMPT" : "Should Packstack install Heat",
|
||||||
|
"OPTION_LIST" : ["y", "n"],
|
||||||
|
"VALIDATORS" : [validators.validate_options],
|
||||||
|
"DEFAULT_VALUE" : "n",
|
||||||
|
"MASK_INPUT" : False,
|
||||||
|
"LOOSE_VALIDATION": False,
|
||||||
|
"CONF_NAME" : "CONFIG_HEAT_INSTALL",
|
||||||
|
"USE_DEFAULT" : False,
|
||||||
|
"NEED_CONFIRM" : False,
|
||||||
|
"CONDITION" : False },
|
||||||
{"CMD_OPTION" : "os-client-install",
|
{"CMD_OPTION" : "os-client-install",
|
||||||
"USAGE" : "Set to 'y' if you would like Packstack to install the OpenStack Client packages. An admin \"rc\" file will also be installed",
|
"USAGE" : "Set to 'y' if you would like Packstack to install the OpenStack Client packages. An admin \"rc\" file will also be installed",
|
||||||
"PROMPT" : "Should Packstack install OpenStack client tools",
|
"PROMPT" : "Should Packstack install OpenStack client tools",
|
||||||
|
@ -77,7 +77,7 @@ def installdeps(config):
|
|||||||
def copyPuppetModules(config):
|
def copyPuppetModules(config):
|
||||||
os_modules = ' '.join(('apache', 'ceilometer', 'cinder', 'concat',
|
os_modules = ' '.join(('apache', 'ceilometer', 'cinder', 'concat',
|
||||||
'create_resources', 'firewall', 'glance',
|
'create_resources', 'firewall', 'glance',
|
||||||
'horizon', 'inifile', 'keystone',
|
'heat', 'horizon', 'inifile', 'keystone',
|
||||||
'memcached', 'mongodb', 'mysql', 'neutron',
|
'memcached', 'mongodb', 'mysql', 'neutron',
|
||||||
'nova', 'openstack', 'packstack', 'qpid',
|
'nova', 'openstack', 'packstack', 'qpid',
|
||||||
'rsync', 'ssh', 'stdlib', 'swift', 'sysctl',
|
'rsync', 'ssh', 'stdlib', 'swift', 'sysctl',
|
||||||
|
1
packstack/puppet/modules/heat
Submodule
1
packstack/puppet/modules/heat
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 84d623b0d38bcb136b7ba0948d3fd066dac4c637
|
23
packstack/puppet/templates/heat.pp
Normal file
23
packstack/puppet/templates/heat.pp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
class { 'heat':
|
||||||
|
keystone_host => '%(CONFIG_KEYSTONE_HOST)s',
|
||||||
|
keystone_password => '%(CONFIG_HEAT_KS_PW)s',
|
||||||
|
auth_uri => 'http://%(CONFIG_KEYSTONE_HOST)s:35357/v2.0',
|
||||||
|
rpc_backend => 'heat.openstack.common.rpc.impl_qpid',
|
||||||
|
qpid_hostname => '%(CONFIG_QPID_HOST)s',
|
||||||
|
verbose => true,
|
||||||
|
debug => true
|
||||||
|
}
|
||||||
|
|
||||||
|
class {"heat::db":
|
||||||
|
sql_connection => "mysql://heat:%(CONFIG_HEAT_DB_PW)s@%(CONFIG_MYSQL_HOST)s/heat"
|
||||||
|
}
|
||||||
|
|
||||||
|
class { 'heat::api':
|
||||||
|
}
|
||||||
|
|
||||||
|
class { 'heat::engine':
|
||||||
|
heat_metadata_server_url => 'http://%(CONFIG_HEAT_METADATA_HOST)s:8000',
|
||||||
|
heat_waitcondition_server_url => 'http://%(CONFIG_HEAT_METADATA_HOST)s:8000/v1/waitcondition',
|
||||||
|
heat_watch_server_url => 'http://%(CONFIG_HEAT_WATCH_HOST)s:8003',
|
||||||
|
}
|
18
packstack/puppet/templates/heat_cfn.pp
Normal file
18
packstack/puppet/templates/heat_cfn.pp
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
class { 'heat':
|
||||||
|
keystone_host => '%(CONFIG_KEYSTONE_HOST)s',
|
||||||
|
keystone_password => '%(CONFIG_HEAT_KS_PW)s',
|
||||||
|
auth_uri => 'http://%(CONFIG_KEYSTONE_HOST)s:35357/v2.0',
|
||||||
|
rpc_backend => 'heat.openstack.common.rpc.impl_qpid',
|
||||||
|
qpid_hostname => '%(CONFIG_QPID_HOST)s',
|
||||||
|
verbose => true,
|
||||||
|
debug => true
|
||||||
|
}
|
||||||
|
|
||||||
|
class {"heat::db":
|
||||||
|
sql_connection => "mysql://heat:%(CONFIG_HEAT_DB_PW)s@%(CONFIG_MYSQL_HOST)s/heat"
|
||||||
|
}
|
||||||
|
|
||||||
|
class { 'heat::api_cfn':
|
||||||
|
}
|
||||||
|
|
17
packstack/puppet/templates/heat_cloudwatch.pp
Normal file
17
packstack/puppet/templates/heat_cloudwatch.pp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
class { 'heat':
|
||||||
|
keystone_host => '%(CONFIG_KEYSTONE_HOST)s',
|
||||||
|
keystone_password => '%(CONFIG_HEAT_KS_PW)s',
|
||||||
|
auth_uri => 'http://%(CONFIG_KEYSTONE_HOST)s:35357/v2.0',
|
||||||
|
rpc_backend => 'heat.openstack.common.rpc.impl_qpid',
|
||||||
|
qpid_hostname => '%(CONFIG_QPID_HOST)s',
|
||||||
|
verbose => true,
|
||||||
|
debug => true
|
||||||
|
}
|
||||||
|
|
||||||
|
class {"heat::db":
|
||||||
|
sql_connection => "mysql://heat:%(CONFIG_HEAT_DB_PW)s@%(CONFIG_MYSQL_HOST)s/heat"
|
||||||
|
}
|
||||||
|
|
||||||
|
class { 'heat::api_cloudwatch':
|
||||||
|
}
|
20
packstack/puppet/templates/keystone_heat.pp
Normal file
20
packstack/puppet/templates/keystone_heat.pp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
if '%(CONFIG_HEAT_CFN_INSTALL)s' == 'y' {
|
||||||
|
class {"heat::keystone::auth":
|
||||||
|
password => "%(CONFIG_HEAT_KS_PW)s",
|
||||||
|
heat_public_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
heat_admin_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
heat_internal_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
cfn_public_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
cfn_admin_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
cfn_internal_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
class {"heat::keystone::auth":
|
||||||
|
password => "%(CONFIG_HEAT_KS_PW)s",
|
||||||
|
heat_public_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
heat_admin_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
heat_internal_address => "%(CONFIG_HEAT_HOST)s",
|
||||||
|
cfn_auth_name => undef,
|
||||||
|
}
|
||||||
|
}
|
4
packstack/puppet/templates/mysql_heat_install.pp
Normal file
4
packstack/puppet/templates/mysql_heat_install.pp
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class {"heat::db::mysql":
|
||||||
|
password => "%(CONFIG_HEAT_DB_PW)s",
|
||||||
|
allowed_hosts => "%%",
|
||||||
|
}
|
27
packstack/puppet/templates/mysql_heat_noinstall.pp
Normal file
27
packstack/puppet/templates/mysql_heat_noinstall.pp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
remote_database { 'heat':
|
||||||
|
ensure => 'present',
|
||||||
|
charset => 'latin1',
|
||||||
|
db_host => '%(CONFIG_MYSQL_HOST)s',
|
||||||
|
db_user => '%(CONFIG_MYSQL_USER)s',
|
||||||
|
db_password => '%(CONFIG_MYSQL_PW)s',
|
||||||
|
provider => 'mysql',
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_database_user { 'heat@%%':
|
||||||
|
password_hash => mysql_password('%(CONFIG_HEAT_DB_PW)s'),
|
||||||
|
db_host => '%(CONFIG_MYSQL_HOST)s',
|
||||||
|
db_user => '%(CONFIG_MYSQL_USER)s',
|
||||||
|
db_password => '%(CONFIG_MYSQL_PW)s',
|
||||||
|
provider => 'mysql',
|
||||||
|
require => Remote_database['heat'],
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_database_grant { 'heat@%%/heat':
|
||||||
|
privileges => "all",
|
||||||
|
db_host => '%(CONFIG_MYSQL_HOST)s',
|
||||||
|
db_user => '%(CONFIG_MYSQL_USER)s',
|
||||||
|
db_password => '%(CONFIG_MYSQL_PW)s',
|
||||||
|
provider => 'mysql',
|
||||||
|
require => Remote_database_user['heat@%%'],
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user