Config-generator for monasca-api
Change migrates the monasca-api to use oslo-config-generator in order to always build newest configuration file. That removes the need of maintaning the file along with changes to oslo configuration. The example of the file is also included in the documentation. Also: * ported case for launching api under WSGI and allowing the argument parsing of oslo to take place Story: 2000970 Task: 4865 Story: 2000964 Task: 4106 Change-Id: I57547b0e2122e40f58db5f949773900b76214526
This commit is contained in:
parent
d2e653d7ec
commit
5d27af4079
2
.gitignore
vendored
2
.gitignore
vendored
@ -33,3 +33,5 @@ db/config.yml
|
||||
virtenv/*
|
||||
.vagrant
|
||||
AUTHORS
|
||||
|
||||
*.sample
|
||||
|
4
AUTHORS
4
AUTHORS
@ -1,3 +1,4 @@
|
||||
Adrian Czarnecki <adrian.czarnecki@ts.fujitsu.com>
|
||||
Andrea Adams <aadams@hpe.com>
|
||||
Andreas Jaeger <aj@suse.com>
|
||||
Angelo Mendonca <angelomendonca@gmail.com>
|
||||
@ -29,6 +30,7 @@ Flávio Ramalho <flaviosr@lsd.ufcg.edu.br>
|
||||
Ghanshyam <ghanshyam.mann@nectechnologies.in>
|
||||
Habeeb Mohammed <habeeb.mohammed@hpe.com>
|
||||
Haiwei Xu <xu-haiwei@mxw.nes.nec.co.jp>
|
||||
Hangdong Zhang <hdzhang@fiberhome.com>
|
||||
Hironori Shiina <shiina.hironori@jp.fujitsu.com>
|
||||
Igor Natanael <igornsa@lsd.ufcg.edu.br>
|
||||
Jakub Wachowski <jakub.wachowski@ts.fujitsu.com>
|
||||
@ -52,6 +54,7 @@ Michael James Hoppal <michael.jam.hoppal@hp.com>
|
||||
Michal Zielonka <michal.zielonka@ts.fujitsu.com>
|
||||
Monty Taylor <mordred@inaugust.com>
|
||||
Nam Nguyen Hoai <namnh@vn.fujitsu.com>
|
||||
OpenStack Release Bot <infra-root@openstack.org>
|
||||
Pradeep Kumar Velusamy <PradeepKumar.V@cognizant.com>
|
||||
Rob Raymond <rob.raymond@hp.com>
|
||||
Rodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
|
||||
@ -90,6 +93,7 @@ liu-sheng <liusheng@huawei.com>
|
||||
liyingjun <yingjun.li@kylin-cloud.com>
|
||||
loooosy <syluo5695@fiberhome.com>
|
||||
melissaml <ma.lei@99cloud.net>
|
||||
mhoppal <michael.jam.hoppal@hpe.com>
|
||||
oiskam1 <oiskam1@yandex.ru>
|
||||
pallavi <pallavi.s@nectechnologies.in>
|
||||
raymondr <raymondr@users.noreply.github.com>
|
||||
|
7
config-generator/README.rst
Normal file
7
config-generator/README.rst
Normal file
@ -0,0 +1,7 @@
|
||||
================
|
||||
config-generator
|
||||
================
|
||||
|
||||
To generate sample configuration file execute::
|
||||
|
||||
tox -e genconfig
|
0
config-generator/__init__.py
Normal file
0
config-generator/__init__.py
Normal file
8
config-generator/api-config.conf
Normal file
8
config-generator/api-config.conf
Normal file
@ -0,0 +1,8 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/api-config.conf.sample
|
||||
wrap_width = 79
|
||||
format = ini
|
||||
summarize = True
|
||||
namespace = monasca_api
|
||||
namespace = oslo.log
|
||||
namespace = oslo.db
|
@ -789,28 +789,45 @@ function configure_monasca_api_python {
|
||||
|
||||
sudo chmod 0775 /var/log/monasca/api
|
||||
|
||||
# create configuration files in target locations
|
||||
rm -rf $MONASCA_API_CONF $MONASCA_API_PASTE_INI $MONASCA_API_LOGGING_CONF
|
||||
$MONASCA_API_BIN_DIR/oslo-config-generator \
|
||||
--config-file $MONASCA_API_DIR/config-generator/api-config.conf \
|
||||
--output-file /tmp/api.conf
|
||||
|
||||
install -m 600 /tmp/api.conf $MONASCA_API_CONF && rm -rf /tmp/api.conf
|
||||
install -m 600 $MONASCA_API_DIR/etc/api-logging.conf $MONASCA_API_LOGGING_CONF
|
||||
install -m 600 $MONASCA_API_DIR/etc/api-config.ini $MONASCA_API_PASTE_INI
|
||||
# create configuration files in target locations
|
||||
|
||||
local dbAlarmUrl
|
||||
local dbMetricDriver
|
||||
|
||||
if [[ "${MONASCA_METRICS_DB,,}" == 'cassandra' ]]; then
|
||||
dbMetricDriver="monasca_api.common.repositories.cassandra.metrics_repository:MetricsRepository"
|
||||
else
|
||||
dbMetricDriver="monasca_api.common.repositories.influxdb.metrics_repository:MetricsRepository"
|
||||
fi
|
||||
dbAlarmUrl=`database_connection_url mon`
|
||||
if [[ "$MONASCA_API_CONF_DIR" != "$MONASCA_API_DIR/etc/monasca" ]]; then
|
||||
install -m 600 $MONASCA_API_DIR/etc/api-config.conf $MONASCA_API_CONF
|
||||
install -m 600 $MONASCA_API_DIR/etc/api-logging.conf $MONASCA_API_LOGGING_CONF
|
||||
install -m 600 $MONASCA_API_DIR/etc/api-config.ini $MONASCA_API_PASTE_INI
|
||||
fi
|
||||
|
||||
# default settings
|
||||
iniset "$MONASCA_API_CONF" DEFAULT region $REGION_NAME
|
||||
iniset "$MONASCA_API_CONF" DEFAULT log_config_append $MONASCA_API_LOGGING_CONF
|
||||
|
||||
# logging
|
||||
iniset "$MONASCA_API_LOGGING_CONF" handler_file args "('$MONASCA_API_LOG_DIR/monasca-api.log', 'a', 104857600, 5)"
|
||||
|
||||
# messaging
|
||||
iniset "$MONASCA_API_CONF" messaging driver "monasca_api.common.messaging.kafka_publisher:KafkaPublisher"
|
||||
iniset "$MONASCA_API_CONF" kafka uri "$SERVICE_HOST:9092"
|
||||
|
||||
# databases
|
||||
iniset "$MONASCA_API_CONF" database connection $dbAlarmUrl
|
||||
iniset "$MONASCA_API_CONF" repositories metrics_driver $dbMetricDriver
|
||||
iniset "$MONASCA_API_CONF" cassandra cluster_ip_addresses $SERVICE_HOST
|
||||
iniset "$MONASCA_API_CONF" influxdb ip_address $SERVICE_HOST
|
||||
iniset "$MONASCA_API_CONF" influxdb port 8086
|
||||
iniset "$MONASCA_API_CONF" kafka uri "$SERVICE_HOST:9092"
|
||||
|
||||
# keystone & security
|
||||
configure_auth_token_middleware $MONASCA_API_CONF "admin" $MONASCA_API_CACHE_DIR
|
||||
iniset "$MONASCA_API_CONF" keystone_authtoken region_name $REGION_NAME
|
||||
iniset "$MONASCA_API_CONF" keystone_authtoken project_name "admin"
|
||||
@ -818,12 +835,16 @@ function configure_monasca_api_python {
|
||||
iniset "$MONASCA_API_CONF" keystone_authtoken identity_uri "http://$SERVICE_HOST:35357"
|
||||
iniset "$MONASCA_API_CONF" keystone_authtoken auth_uri "http://$SERVICE_HOST:5000"
|
||||
|
||||
iniset "$MONASCA_API_CONF" security default_authorized_roles "user, domainuser, domainadmin, monasca-user"
|
||||
iniset "$MONASCA_API_CONF" security agent_authorized_roles "monasca-agent"
|
||||
iniset "$MONASCA_API_CONF" security read_only_authorized_roles "monasca-read-only-user"
|
||||
iniset "$MONASCA_API_CONF" security delegate_authorized_roles "admin"
|
||||
|
||||
# server setup
|
||||
iniset "$MONASCA_API_PASTE_INI" server:main host $MONASCA_API_SERVICE_HOST
|
||||
iniset "$MONASCA_API_PASTE_INI" server:main port $MONASCA_API_SERVICE_PORT
|
||||
iniset "$MONASCA_API_PASTE_INI" server:main workers $API_WORKERS
|
||||
|
||||
iniset "$MONASCA_API_LOGGING_CONF" handler_file args "('$MONASCA_API_LOG_DIR/monasca-api.log', 'a', 104857600, 5)"
|
||||
|
||||
# link configuration for the gate
|
||||
ln -sf $MONASCA_API_CONF $MON_API_GATE_CONFIGURATION_DIR
|
||||
ln -sf $MONASCA_API_PASTE_INI $MON_API_GATE_CONFIGURATION_DIR
|
||||
|
@ -36,9 +36,8 @@ extensions = [
|
||||
'sphinx.ext.graphviz',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
# TODO(trebskit) enable as soon as we get configgen in place
|
||||
# 'oslo_config.sphinxconfiggen'
|
||||
# 'oslo_config.sphinxext',
|
||||
'oslo_config.sphinxconfiggen',
|
||||
'oslo_config.sphinxext',
|
||||
'openstackdocstheme',
|
||||
]
|
||||
|
||||
@ -52,6 +51,11 @@ bug_tag = u''
|
||||
copyright = u'2014-present, OpenStack Foundation'
|
||||
author = u'OpenStack Foundation'
|
||||
|
||||
# sample config
|
||||
config_generator_config_file = [
|
||||
('config-generator/api-config.conf', '_static/api-config')
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
.. _configuring:
|
||||
|
||||
=============
|
||||
Configuration
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
sample
|
||||
|
40
doc/source/configuration/sample.rst
Normal file
40
doc/source/configuration/sample.rst
Normal file
@ -0,0 +1,40 @@
|
||||
.. _sample-configuration:
|
||||
|
||||
-------
|
||||
Samples
|
||||
-------
|
||||
|
||||
The following sections show sample configuration files for monasca-api and
|
||||
related utilities. These are generated from the code
|
||||
(apart from the samples for logging and paster) and reflect the current state
|
||||
of code in the monasca-api repository.
|
||||
|
||||
|
||||
.. _sample-configuration-api:
|
||||
|
||||
Sample Configuration For Application
|
||||
------------------------------------
|
||||
|
||||
This sample configuration can also be viewed in `api-config.conf.sample
|
||||
<../_static/api-config.conf.sample>`_.
|
||||
|
||||
.. literalinclude:: ../_static/api-config.conf.sample
|
||||
|
||||
.. _sample-configuration-logging:
|
||||
|
||||
Sample Configuration For Logging
|
||||
--------------------------------
|
||||
|
||||
This sample configuration can also be viewed in `api-logging.conf
|
||||
<../../../etc/api-logging.conf>`_.
|
||||
|
||||
.. literalinclude:: ../../../etc/api-logging.conf
|
||||
|
||||
|
||||
Sample Configuration For Paster
|
||||
-------------------------------
|
||||
|
||||
This sample configuration can also be viewed in `api-config.ini
|
||||
<../../../etc/api-config.ini>`_.
|
||||
|
||||
.. literalinclude:: ../../../etc/api-config.ini
|
@ -1,146 +0,0 @@
|
||||
[DEFAULT]
|
||||
log_config_append=/etc/monasca/api-logging.conf
|
||||
|
||||
# Identifies the region that the Monasca API is running in.
|
||||
region = useast
|
||||
|
||||
# Dispatchers to be loaded to serve restful APIs
|
||||
[dispatcher]
|
||||
versions = monasca_api.v2.reference.versions:Versions
|
||||
version_2_0 = monasca_api.v2.reference.version_2_0:Version2
|
||||
metrics = monasca_api.v2.reference.metrics:Metrics
|
||||
metrics_measurements = monasca_api.v2.reference.metrics:MetricsMeasurements
|
||||
metrics_statistics = monasca_api.v2.reference.metrics:MetricsStatistics
|
||||
metrics_names = monasca_api.v2.reference.metrics:MetricsNames
|
||||
alarm_definitions = monasca_api.v2.reference.alarm_definitions:AlarmDefinitions
|
||||
alarms = monasca_api.v2.reference.alarms:Alarms
|
||||
alarms_count = monasca_api.v2.reference.alarms:AlarmsCount
|
||||
alarms_state_history = monasca_api.v2.reference.alarms:AlarmsStateHistory
|
||||
notification_methods = monasca_api.v2.reference.notifications:Notifications
|
||||
dimension_values = monasca_api.v2.reference.metrics:DimensionValues
|
||||
dimension_names = monasca_api.v2.reference.metrics:DimensionNames
|
||||
notification_method_types = monasca_api.v2.reference.notificationstype:NotificationsType
|
||||
healthchecks = monasca_api.healthchecks:HealthChecks
|
||||
|
||||
[security]
|
||||
# The roles that are allowed full access to the API.
|
||||
default_authorized_roles = user, domainuser, domainadmin, monasca-user
|
||||
|
||||
# The roles that are allowed to only POST metrics to the API. This role would be used by the Monasca Agent.
|
||||
agent_authorized_roles = monasca-agent
|
||||
|
||||
# The roles that are allowed to only GET metrics from the API.
|
||||
read_only_authorized_roles = monasca-read-only-user
|
||||
|
||||
# The roles that are allowed to access the API on behalf of another tenant.
|
||||
# For example, a service can POST metrics to another tenant if they are a member of the "delegate" role.
|
||||
delegate_authorized_roles = admin
|
||||
|
||||
[messaging]
|
||||
# The message queue driver to use
|
||||
driver = monasca_api.common.messaging.kafka_publisher:KafkaPublisher
|
||||
|
||||
[repositories]
|
||||
# The driver to use for the metrics repository
|
||||
# Switches depending on backend database in use. Influxdb or Cassandra.
|
||||
metrics_driver = monasca_api.common.repositories.influxdb.metrics_repository:MetricsRepository
|
||||
#metrics_driver = monasca_api.common.repositories.cassandra.metrics_repository:MetricsRepository
|
||||
|
||||
# The driver to use for the alarm definitions repository
|
||||
alarm_definitions_driver = monasca_api.common.repositories.sqla.alarm_definitions_repository:AlarmDefinitionsRepository
|
||||
|
||||
# The driver to use for the alarms repository
|
||||
alarms_driver = monasca_api.common.repositories.sqla.alarms_repository:AlarmsRepository
|
||||
|
||||
# The driver to use for the notifications repository
|
||||
notifications_driver = monasca_api.common.repositories.sqla.notifications_repository:NotificationsRepository
|
||||
|
||||
# The driver to use for the notification method type repository
|
||||
notification_method_type_driver = monasca_api.common.repositories.sqla.notification_method_type_repository:NotificationMethodTypeRepository
|
||||
|
||||
|
||||
[dispatcher]
|
||||
driver = v2_reference
|
||||
|
||||
[kafka]
|
||||
# The endpoint to the kafka server
|
||||
uri = 192.168.10.4:9092
|
||||
|
||||
# The topic that metrics will be published too
|
||||
metrics_topic = metrics
|
||||
|
||||
# The topic that events will be published too
|
||||
events_topic = events
|
||||
|
||||
# The topic that alarm state will be published too
|
||||
alarm_state_transitions_topic = alarm-state-transitions
|
||||
|
||||
# consumer group name
|
||||
group = api
|
||||
|
||||
# how many times to try when error occurs
|
||||
max_retry = 1
|
||||
|
||||
# wait time between tries when kafka goes down
|
||||
wait_time = 1
|
||||
|
||||
# use synchronous or asynchronous connection to kafka
|
||||
async = False
|
||||
|
||||
# send messages in bulk or send messages one by one.
|
||||
compact = False
|
||||
|
||||
# How many partitions this connection should listen messages on, this
|
||||
# parameter is for reading from kafka. If listens on multiple partitions,
|
||||
# For example, if the client should listen on partitions 1 and 3, then the
|
||||
# configuration should look like the following:
|
||||
# partitions = 1
|
||||
# partitions = 3
|
||||
# default to listen on partition 0.
|
||||
partitions = 0
|
||||
|
||||
[influxdb]
|
||||
# Only needed if Influxdb database is used for backend.
|
||||
# The IP address of the InfluxDB service.
|
||||
ip_address = 192.168.10.4
|
||||
|
||||
# The port number that the InfluxDB service is listening on.
|
||||
port = 8086
|
||||
|
||||
# The username to authenticate with.
|
||||
user = mon_api
|
||||
|
||||
# The password to authenticate with.
|
||||
password = password
|
||||
|
||||
# The name of the InfluxDB database to use.
|
||||
database_name = mon
|
||||
|
||||
[cassandra]
|
||||
# Only needed if Cassandra database is used for backend.
|
||||
# Comma separated list of Cassandra node IP addresses. No spaces.
|
||||
cluster_ip_addresses: 192.168.10.6
|
||||
keyspace: monasca
|
||||
|
||||
# Below is configuration for database.
|
||||
[database]
|
||||
connection = "mysql+pymysql://monapi:password@192.168.10.4/mon?charset=utf8mb4"
|
||||
# backend = sqlalchemy
|
||||
# host = 192.168.10.4
|
||||
# username = monapi
|
||||
# password = password
|
||||
# drivername = mysq+pymysql
|
||||
# port = 3306
|
||||
# database = mon
|
||||
# query = ""
|
||||
|
||||
[keystone_authtoken]
|
||||
identity_uri = http://192.168.10.5:35357
|
||||
auth_uri = http://192.168.10.5:5000
|
||||
admin_password = admin
|
||||
admin_user = admin
|
||||
admin_tenant_name = admin
|
||||
cafile =
|
||||
certfile =
|
||||
keyfile =
|
||||
insecure = false
|
@ -23,63 +23,14 @@ from oslo_log import log
|
||||
import paste.deploy
|
||||
|
||||
from monasca_api.api.core import request
|
||||
|
||||
dispatcher_opts = [cfg.StrOpt('versions', default=None,
|
||||
help='Versions'),
|
||||
cfg.StrOpt('version_2_0', default=None,
|
||||
help='Version 2.0'),
|
||||
cfg.StrOpt('metrics', default=None,
|
||||
help='Metrics'),
|
||||
cfg.StrOpt('metrics_measurements', default=None,
|
||||
help='Metrics measurements'),
|
||||
cfg.StrOpt('metrics_statistics', default=None,
|
||||
help='Metrics statistics'),
|
||||
cfg.StrOpt('metrics_names', default=None,
|
||||
help='Metrics names'),
|
||||
cfg.StrOpt('alarm_definitions', default=None,
|
||||
help='Alarm definitions'),
|
||||
cfg.StrOpt('alarms', default=None,
|
||||
help='Alarms'),
|
||||
cfg.StrOpt('alarms_count', default=None,
|
||||
help='Alarms Count'),
|
||||
cfg.StrOpt('alarms_state_history', default=None,
|
||||
help='Alarms state history'),
|
||||
cfg.StrOpt('notification_methods', default=None,
|
||||
help='Notification methods'),
|
||||
cfg.StrOpt('dimension_values', default=None,
|
||||
help='Dimension values'),
|
||||
cfg.StrOpt('dimension_names', default=None,
|
||||
help='Dimension names'),
|
||||
cfg.StrOpt('notification_method_types', default=None,
|
||||
help='notification_method_types methods'),
|
||||
cfg.StrOpt('healthchecks', default=None,
|
||||
help='Health checks endpoint')]
|
||||
|
||||
dispatcher_group = cfg.OptGroup(name='dispatcher', title='dispatcher')
|
||||
cfg.CONF.register_group(dispatcher_group)
|
||||
cfg.CONF.register_opts(dispatcher_opts, dispatcher_group)
|
||||
from monasca_api import config
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
_CONF_LOADED = False
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
def launch(conf):
|
||||
global _CONF_LOADED
|
||||
if not _CONF_LOADED:
|
||||
# use default, but try to access one passed from conf first
|
||||
config_file = conf.get('config_file', "/etc/monasca/api-config.conf")
|
||||
|
||||
log.register_options(cfg.CONF)
|
||||
log.set_defaults()
|
||||
cfg.CONF(args=[],
|
||||
project='monasca_api',
|
||||
default_config_files=[config_file])
|
||||
log.setup(cfg.CONF, 'monasca_api')
|
||||
LOG.debug('Configuration loaded successfully')
|
||||
_CONF_LOADED = True
|
||||
else:
|
||||
LOG.debug('Configuration has already been loaded')
|
||||
config.parse_args()
|
||||
|
||||
app = falcon.API(request_type=request.Request)
|
||||
|
||||
|
@ -38,13 +38,12 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
|
||||
try:
|
||||
|
||||
self.conf = cfg.CONF
|
||||
|
||||
self._cassandra_cluster = Cluster(
|
||||
self.conf.cassandra.cluster_ip_addresses.split(','))
|
||||
|
||||
self.conf.cassandra.cluster_ip_addresses
|
||||
)
|
||||
self.cassandra_session = self._cassandra_cluster.connect(
|
||||
self.conf.cassandra.keyspace)
|
||||
|
||||
self.conf.cassandra.keyspace
|
||||
)
|
||||
except Exception as ex:
|
||||
LOG.exception(ex)
|
||||
raise exceptions.RepositoryException(ex)
|
||||
|
82
monasca_api/conf/__init__.py
Normal file
82
monasca_api/conf/__init__.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import pkgutil
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import options as oslo_db_opts
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def load_conf_modules():
|
||||
"""Loads all modules that contain configuration.
|
||||
|
||||
Method iterates over modules of :py:module:`monasca_api.conf`
|
||||
and imports only those that contain following methods:
|
||||
|
||||
- list_opts (required by oslo_config.genconfig)
|
||||
- register_opts (required by :py:currentmodule:)
|
||||
|
||||
"""
|
||||
for modname in _list_module_names():
|
||||
mod = importutils.import_module('monasca_api.conf.' + modname)
|
||||
required_funcs = ['register_opts', 'list_opts']
|
||||
for func in required_funcs:
|
||||
if hasattr(mod, func):
|
||||
yield mod
|
||||
|
||||
|
||||
def _list_module_names():
|
||||
package_path = os.path.dirname(os.path.abspath(__file__))
|
||||
for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
|
||||
if not (modname == "opts" and ispkg):
|
||||
yield modname
|
||||
|
||||
|
||||
def register_opts():
|
||||
"""Registers all conf modules opts.
|
||||
|
||||
This method allows different modules to register
|
||||
opts according to their needs.
|
||||
|
||||
"""
|
||||
_register_api_opts()
|
||||
_register_db_opts()
|
||||
|
||||
|
||||
def _register_api_opts():
|
||||
for mod in load_conf_modules():
|
||||
mod.register_opts(CONF)
|
||||
|
||||
|
||||
def _register_db_opts():
|
||||
oslo_db_opts.set_defaults(CONF, connection='sqlite://',
|
||||
max_pool_size=10, max_overflow=20,
|
||||
pool_timeout=10)
|
||||
|
||||
|
||||
def list_opts():
|
||||
"""Lists all conf modules opts.
|
||||
|
||||
Goes through all conf modules and yields their opts
|
||||
|
||||
"""
|
||||
for mod in load_conf_modules():
|
||||
mod_opts = mod.list_opts()
|
||||
yield mod_opts[0], mod_opts[1]
|
42
monasca_api/conf/cassandra.py
Normal file
42
monasca_api/conf/cassandra.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import types
|
||||
|
||||
cassandra_opts = [
|
||||
cfg.ListOpt('cluster_ip_addresses',
|
||||
default=['127.0.0.1'],
|
||||
item_type=types.HostAddress(),
|
||||
help='''
|
||||
Comma separated list of Cassandra node IP addresses
|
||||
'''),
|
||||
cfg.StrOpt('keyspace', default='monasca',
|
||||
help='''
|
||||
keyspace where metric are stored
|
||||
''')
|
||||
]
|
||||
|
||||
cassandra_group = cfg.OptGroup(name='cassandra')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(cassandra_group)
|
||||
conf.register_opts(cassandra_opts, cassandra_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return cassandra_group, cassandra_opts
|
35
monasca_api/conf/database.py
Normal file
35
monasca_api/conf/database.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
url_opt = cfg.StrOpt(name='url',
|
||||
default='$database.connection',
|
||||
help='''
|
||||
The SQLAlchemy connection string to use to connect to the database
|
||||
''',
|
||||
required=False,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='1.6.0',
|
||||
deprecated_reason='Please use database.connection option,'
|
||||
'database.url is scheduled for removal '
|
||||
'in Pike release')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opt(url_opt, 'database')
|
||||
|
||||
|
||||
def list_opts():
|
||||
return 'database', [url_opt]
|
78
monasca_api/conf/dispatcher.py
Normal file
78
monasca_api/conf/dispatcher.py
Normal file
@ -0,0 +1,78 @@
|
||||
# Copyright 2014 IBM Corp
|
||||
# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 Fujitsu LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
dispatcher_opts = [
|
||||
cfg.StrOpt('versions',
|
||||
default='monasca_api.v2.reference.versions:Versions',
|
||||
help='Versions controller'),
|
||||
cfg.StrOpt('version_2_0',
|
||||
default='monasca_api.v2.reference.version_2_0:Version2',
|
||||
help='Version 2.0 controller'),
|
||||
cfg.StrOpt('metrics',
|
||||
default='monasca_api.v2.reference.metrics:Metrics',
|
||||
help='Metrics controller'),
|
||||
cfg.StrOpt('metrics_measurements',
|
||||
default='monasca_api.v2.reference.metrics:MetricsMeasurements',
|
||||
help='Metrics measurements controller'),
|
||||
cfg.StrOpt('metrics_statistics',
|
||||
default='monasca_api.v2.reference.metrics:MetricsStatistics',
|
||||
help='Metrics statistics controller'),
|
||||
cfg.StrOpt('metrics_names',
|
||||
default='monasca_api.v2.reference.metrics:MetricsNames',
|
||||
help='Metrics names controller'),
|
||||
cfg.StrOpt('alarm_definitions',
|
||||
default='monasca_api.v2.reference.'
|
||||
'alarm_definitions:AlarmDefinitions',
|
||||
help='Alarm definitions controller'),
|
||||
cfg.StrOpt('alarms',
|
||||
default='monasca_api.v2.reference.alarms:Alarms',
|
||||
help='Alarms controller'),
|
||||
cfg.StrOpt('alarms_count',
|
||||
default='monasca_api.v2.reference.alarms:AlarmsCount',
|
||||
help='Alarms Count controller'),
|
||||
cfg.StrOpt('alarms_state_history',
|
||||
default='monasca_api.v2.reference.alarms:AlarmsStateHistory',
|
||||
help='Alarms state history controller'),
|
||||
cfg.StrOpt('notification_methods',
|
||||
default='monasca_api.v2.reference.notifications:Notifications',
|
||||
help='Notification Methods controller'),
|
||||
cfg.StrOpt('dimension_values',
|
||||
default='monasca_api.v2.reference.metrics:DimensionValues',
|
||||
help='Dimension Values controller'),
|
||||
cfg.StrOpt('dimension_names',
|
||||
default='monasca_api.v2.reference.metrics:DimensionNames',
|
||||
help='Dimension Names controller'),
|
||||
cfg.StrOpt('notification_method_types',
|
||||
default='monasca_api.v2.reference.'
|
||||
'notificationstype:NotificationsType',
|
||||
help='Notifications Type Methods controller'),
|
||||
cfg.StrOpt('healthchecks',
|
||||
default='monasca_api.healthchecks:HealthChecks',
|
||||
help='Health checks endpoint controller')
|
||||
]
|
||||
|
||||
dispatcher_group = cfg.OptGroup(name='dispatcher', title='dispatcher')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(dispatcher_group)
|
||||
conf.register_opts(dispatcher_opts, dispatcher_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return dispatcher_group, dispatcher_opts
|
39
monasca_api/conf/global.py
Normal file
39
monasca_api/conf/global.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
_DEFAULT_NOTIF_PERIODS = [0, 60]
|
||||
|
||||
global_opts = [
|
||||
cfg.StrOpt('region', sample_default='RegionOne',
|
||||
help='''
|
||||
Region that API is running in
|
||||
'''),
|
||||
cfg.ListOpt('valid_notification_periods', default=_DEFAULT_NOTIF_PERIODS,
|
||||
item_type=int,
|
||||
help='''
|
||||
Valid periods for notification methods
|
||||
''')
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(global_opts)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return 'DEFAULT', global_opts
|
50
monasca_api/conf/influxdb.py
Normal file
50
monasca_api/conf/influxdb.py
Normal file
@ -0,0 +1,50 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
influxdb_opts = [
|
||||
cfg.StrOpt('database_name', default='mon',
|
||||
help='''
|
||||
Database name where metrics are stored
|
||||
'''),
|
||||
cfg.HostAddressOpt('ip_address', default='127.0.0.1',
|
||||
help='''
|
||||
IP address to Influxdb server
|
||||
'''),
|
||||
cfg.PortOpt('port', default=8086,
|
||||
help='Port to Influxdb server'),
|
||||
cfg.StrOpt('user', required=True,
|
||||
sample_default='monasca-api', help='''
|
||||
Influxdb user
|
||||
'''),
|
||||
cfg.StrOpt('password', secret=True, sample_default='password',
|
||||
help='''
|
||||
Influxdb password
|
||||
''')
|
||||
]
|
||||
|
||||
influxdb_group = cfg.OptGroup(name='influxdb', title='influxdb')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(influxdb_group)
|
||||
conf.register_opts(influxdb_opts, influxdb_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return influxdb_group, influxdb_opts
|
96
monasca_api/conf/kafka.py
Normal file
96
monasca_api/conf/kafka.py
Normal file
@ -0,0 +1,96 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from monasca_api.conf import types
|
||||
|
||||
kafka_opts = [
|
||||
cfg.ListOpt('uri',
|
||||
default=['127.0.0.1:9092'],
|
||||
item_type=types.HostAddressPortType(),
|
||||
help='''
|
||||
Comma separated list of Kafka broker host:port
|
||||
'''),
|
||||
cfg.StrOpt('metrics_topic', default='metrics',
|
||||
help='''
|
||||
The topic that metrics will be published to
|
||||
'''),
|
||||
cfg.StrOpt('events_topic', default='events',
|
||||
help='''
|
||||
The topic that events will be published too
|
||||
'''),
|
||||
cfg.StrOpt('alarm_state_transitions_topic',
|
||||
default='alarm-state-transitions',
|
||||
help='''
|
||||
The topic that alarm state will be published too
|
||||
'''),
|
||||
cfg.StrOpt('group', default='api',
|
||||
help='''
|
||||
The group name that this service belongs to
|
||||
'''),
|
||||
cfg.IntOpt('wait_time', default=1,
|
||||
advanced=True, min=1,
|
||||
help='''
|
||||
The wait time when no messages on kafka queue
|
||||
'''),
|
||||
cfg.IntOpt('ack_time', default=20,
|
||||
help='''
|
||||
The ack time back to kafka.
|
||||
'''),
|
||||
cfg.IntOpt('max_retry', default=3,
|
||||
help='''
|
||||
The number of retry when there is a connection error
|
||||
'''),
|
||||
cfg.BoolOpt('auto_commit', default=False,
|
||||
advanced=True, help='''
|
||||
Should messages be automatically committed
|
||||
'''),
|
||||
cfg.BoolOpt('async', default=True,
|
||||
help='''
|
||||
The type of posting
|
||||
'''),
|
||||
cfg.BoolOpt('compact', default=True,
|
||||
help='''
|
||||
Specify if the message received should be parsed.
|
||||
If True, message will not be parsed, otherwise
|
||||
messages will be parsed
|
||||
'''),
|
||||
cfg.ListOpt('partitions', item_type=int,
|
||||
default=[0], help='''
|
||||
The partitions this connection should
|
||||
listen for messages on. Currently does not
|
||||
support multiple partitions.
|
||||
Default is to listen on partition 0
|
||||
'''),
|
||||
cfg.BoolOpt('drop_data', default=False,
|
||||
help='''
|
||||
Specify if received data should be simply dropped.
|
||||
This parameter is only for testing purposes
|
||||
''')
|
||||
]
|
||||
|
||||
kafka_group = cfg.OptGroup(name='kafka', title='kafka')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(kafka_group)
|
||||
conf.register_opts(kafka_opts, kafka_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return kafka_group, kafka_opts
|
52
monasca_api/conf/messaging.py
Normal file
52
monasca_api/conf/messaging.py
Normal file
@ -0,0 +1,52 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
messaging_opts = [
|
||||
cfg.StrOpt('driver', help='''
|
||||
The message queue driver to use
|
||||
'''),
|
||||
cfg.StrOpt('metrics_message_format', default='reference',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since="2.1.0",
|
||||
deprecated_reason='''
|
||||
Option is not used anywhere in the codebase
|
||||
''',
|
||||
help='''
|
||||
The type of metrics message format to publish to the message queue
|
||||
'''),
|
||||
cfg.StrOpt('events_message_format', default='reference',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='2.1.0',
|
||||
deprecated_reason='''
|
||||
Option is not used anywhere in the codebase
|
||||
''',
|
||||
help='''
|
||||
The type of events message format to publish to the message queue
|
||||
''')
|
||||
]
|
||||
|
||||
messaging_group = cfg.OptGroup(name='messaging', title='messaging')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(messaging_group)
|
||||
conf.register_opts(messaging_opts, messaging_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return messaging_group, messaging_opts
|
64
monasca_api/conf/repositories.py
Normal file
64
monasca_api/conf/repositories.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
BASE_SQL_PATH = 'monasca_api.common.repositories.sqla.'
|
||||
|
||||
repositories_opts = [
|
||||
cfg.StrOpt('metrics_driver',
|
||||
default='monasca_api.common.repositories.influxdb.metrics_repository:MetricsRepository',
|
||||
advanced=True,
|
||||
help='''
|
||||
The repository driver to use for metrics
|
||||
'''),
|
||||
cfg.StrOpt('alarm_definitions_driver',
|
||||
default=BASE_SQL_PATH + 'alarm_definitions_repository:AlarmDefinitionsRepository',
|
||||
advanced=True,
|
||||
help='''
|
||||
The repository driver to use for alarm definitions
|
||||
'''),
|
||||
cfg.StrOpt('alarms_driver',
|
||||
default=BASE_SQL_PATH + 'alarms_repository:AlarmsRepository',
|
||||
advanced=True,
|
||||
help='''
|
||||
The repository driver to use for alarms
|
||||
'''),
|
||||
cfg.StrOpt('notifications_driver',
|
||||
default=BASE_SQL_PATH + 'notifications_repository:NotificationsRepository',
|
||||
advanced=True,
|
||||
help='''
|
||||
The repository driver to use for notifications
|
||||
'''),
|
||||
cfg.StrOpt('notification_method_type_driver',
|
||||
default=BASE_SQL_PATH + 'notification_method_type_repository:NotificationMethodTypeRepository',
|
||||
advanced=True,
|
||||
help='''
|
||||
The repository driver to use for notifications
|
||||
''')
|
||||
]
|
||||
|
||||
repositories_group = cfg.OptGroup(name='repositories', title='repositories')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(repositories_group)
|
||||
conf.register_opts(repositories_opts, repositories_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return repositories_group, repositories_opts
|
49
monasca_api/conf/security.py
Normal file
49
monasca_api/conf/security.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016-2017 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
security_opts = [
|
||||
cfg.ListOpt('default_authorized_roles', default=['admin'],
|
||||
help='''
|
||||
Roles that are allowed full access to the API
|
||||
'''),
|
||||
cfg.ListOpt('agent_authorized_roles', default=['agent'],
|
||||
help='''
|
||||
Roles that are only allowed to POST to the API
|
||||
'''),
|
||||
cfg.ListOpt('read_only_authorized_roles',
|
||||
default=['monasca-read-only-user'],
|
||||
help='''
|
||||
Roles that are only allowed to GET from the API
|
||||
'''),
|
||||
cfg.ListOpt('delegate_authorized_roles', default=['admin'],
|
||||
help='''
|
||||
Roles that are allowed to POST metrics on
|
||||
behalf of another tenant
|
||||
''')
|
||||
]
|
||||
|
||||
security_group = cfg.OptGroup(name='security', title='security')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(security_group)
|
||||
conf.register_opts(security_opts, security_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return security_group, security_opts
|
58
monasca_api/conf/types.py
Normal file
58
monasca_api/conf/types.py
Normal file
@ -0,0 +1,58 @@
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import types
|
||||
|
||||
|
||||
class HostAddressPortOpt(cfg.Opt):
|
||||
"""Option for HostAddressPortType.
|
||||
|
||||
Accept hostname or ip address with TCP/IP port number.
|
||||
"""
|
||||
def __init__(self, name, **kwargs):
|
||||
ip_port_type = HostAddressPortType()
|
||||
super(HostAddressPortOpt, self).__init__(name,
|
||||
type=ip_port_type,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class HostAddressPortType(types.HostAddress):
|
||||
"""HostAddress with additional port."""
|
||||
|
||||
def __init__(self, version=None):
|
||||
type_name = 'ip and port value'
|
||||
super(HostAddressPortType, self).__init__(version, type_name=type_name)
|
||||
|
||||
def __call__(self, value):
|
||||
addr, port = value.split(':')
|
||||
addr = self.validate_addr(addr)
|
||||
port = self._validate_port(port)
|
||||
if not addr and not port:
|
||||
raise ValueError('%s is not valid ip with optional port')
|
||||
return '%s:%d' % (addr, port)
|
||||
|
||||
@staticmethod
|
||||
def _validate_port(port):
|
||||
return types.Port()(port)
|
||||
|
||||
def validate_addr(self, addr):
|
||||
try:
|
||||
addr = self.ip_address(addr)
|
||||
except ValueError:
|
||||
try:
|
||||
addr = self.hostname(addr)
|
||||
except ValueError:
|
||||
raise ValueError("%s is not a valid host address", addr)
|
||||
return addr
|
87
monasca_api/config.py
Normal file
87
monasca_api/config.py
Normal file
@ -0,0 +1,87 @@
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from monasca_api import conf
|
||||
from monasca_api import version
|
||||
|
||||
CONF = conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
_CONF_LOADED = False
|
||||
_GUNICORN_MARKER = 'gunicorn'
|
||||
|
||||
|
||||
def parse_args(argv=None, config_file=None):
|
||||
"""Loads application configuration.
|
||||
|
||||
Loads entire application configuration just once.
|
||||
|
||||
"""
|
||||
global _CONF_LOADED
|
||||
if _CONF_LOADED:
|
||||
LOG.debug('Configuration has been already loaded')
|
||||
return
|
||||
|
||||
log.set_defaults()
|
||||
log.register_options(CONF)
|
||||
|
||||
argv = (argv if argv is not None else sys.argv[1:])
|
||||
args = ([] if _is_running_under_gunicorn() else argv or [])
|
||||
config_file = (_get_deprecated_config_file()
|
||||
if config_file is None else config_file)
|
||||
|
||||
CONF(args=args,
|
||||
prog='api',
|
||||
project='monasca',
|
||||
version=version.version_str,
|
||||
default_config_files=[config_file] if config_file else None,
|
||||
description='RESTful API for alarming in the cloud')
|
||||
|
||||
log.setup(CONF,
|
||||
product_name='monasca-api',
|
||||
version=version.version_str)
|
||||
conf.register_opts()
|
||||
|
||||
_CONF_LOADED = True
|
||||
|
||||
|
||||
def _is_running_under_gunicorn():
|
||||
"""Evaluates if api runs under gunicorn."""
|
||||
content = filter(lambda x: x != sys.executable and _GUNICORN_MARKER in x,
|
||||
sys.argv or [])
|
||||
return len(list(content) if not isinstance(content, list) else content) > 0
|
||||
|
||||
|
||||
def _get_deprecated_config_file():
|
||||
"""Get deprecated config file.
|
||||
|
||||
Responsible for keeping backward compatibility with old name of
|
||||
the configuration file i.e. api-config.conf.
|
||||
New name is => api.conf as prog=api.
|
||||
|
||||
Note:
|
||||
Old configuration file name did not follow a convention
|
||||
oslo_config expects.
|
||||
|
||||
"""
|
||||
old_files = cfg.find_config_files(project='monasca', prog='api-config')
|
||||
if old_files is not None and len(old_files) > 0:
|
||||
LOG.warning('Detected old location "/etc/monasca/api-config.conf" '
|
||||
'of main configuration file')
|
||||
return old_files[0]
|
@ -88,7 +88,7 @@ class MetricsDbCheck(base.BaseHealthCheck):
|
||||
return False, "Cassandra driver not imported"
|
||||
try:
|
||||
cassandra = self._cluster.Cluster(
|
||||
CONF.cassandra.cluster_ip_addresses.split(',')
|
||||
CONF.cassandra.cluster_ip_addresses
|
||||
)
|
||||
session = cassandra.connect(CONF.cassandra.keyspace)
|
||||
session.shutdown()
|
||||
|
@ -21,6 +21,8 @@ from oslo_context import fixture as oo_ctx
|
||||
from oslotest import base as oslotest_base
|
||||
|
||||
from monasca_api.api.core import request
|
||||
from monasca_api import conf
|
||||
from monasca_api import config
|
||||
|
||||
|
||||
class MockedAPI(falcon.API):
|
||||
@ -45,10 +47,21 @@ class ConfigFixture(oo_cfg.Config):
|
||||
"""Mocks configuration"""
|
||||
|
||||
def __init__(self):
|
||||
super(ConfigFixture, self).__init__(cfg.CONF)
|
||||
super(ConfigFixture, self).__init__(config.CONF)
|
||||
|
||||
def setUp(self):
|
||||
super(ConfigFixture, self).setUp()
|
||||
self.addCleanup(self._clean_config_loaded_flag)
|
||||
conf.register_opts()
|
||||
self._set_defaults()
|
||||
config.parse_args(argv=[]) # prevent oslo from parsing test args
|
||||
|
||||
@staticmethod
|
||||
def _clean_config_loaded_flag():
|
||||
config._CONF_LOADED = False
|
||||
|
||||
def _set_defaults(self):
|
||||
self.conf.set_default('user', 'monasca', 'influxdb')
|
||||
|
||||
|
||||
class BaseTestCase(oslotest_base.BaseTestCase):
|
||||
|
40
monasca_api/tests/config.py
Normal file
40
monasca_api/tests/config.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from monasca_api import config
|
||||
from monasca_api.tests import base
|
||||
|
||||
|
||||
class TestConfig(base.BaseTestCase):
|
||||
|
||||
@mock.patch('monasca_log_api.config.sys')
|
||||
def test_should_return_true_if_runs_under_gunicorn(self, sys_patch):
|
||||
sys_patch.argv = [
|
||||
'/bin/gunicorn',
|
||||
'--capture-output',
|
||||
'--paste',
|
||||
'etc/monasca/log-api-paste.ini',
|
||||
'--workers',
|
||||
'1'
|
||||
]
|
||||
sys_patch.executable = '/bin/python'
|
||||
self.assertTrue(config._is_running_under_gunicorn())
|
||||
|
||||
@mock.patch('monasca_log_api.config.sys')
|
||||
def test_should_return_false_if_runs_without_gunicorn(self, sys_patch):
|
||||
sys_patch.argv = ['/bin/monasca-log-api']
|
||||
sys_patch.executable = '/bin/python'
|
||||
self.assertFalse(config._is_running_under_gunicorn())
|
@ -14,11 +14,11 @@
|
||||
|
||||
import mock
|
||||
|
||||
from monasca_api import config
|
||||
from monasca_api.healthcheck import alarms_db_check as rdc
|
||||
from monasca_api.tests import base
|
||||
from monasca_api.v2.reference import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TestMetricsDbHealthCheckLogic(base.BaseTestCase):
|
||||
|
45
monasca_api/tests/test_config_types.py
Normal file
45
monasca_api/tests/test_config_types.py
Normal file
@ -0,0 +1,45 @@
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
# Copyright 2017 FUJITSU LIMITED
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from monasca_api.conf import types
|
||||
from monasca_api.tests import base
|
||||
|
||||
|
||||
class TestHostAddressPortType(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestHostAddressPortType, self).setUp()
|
||||
self.types = types.HostAddressPortType()
|
||||
|
||||
def test_ip_address(self):
|
||||
self.assertEqual('127.0.0.1:2121', self.types('127.0.0.1:2121'))
|
||||
|
||||
def test_hostname(self):
|
||||
self.assertEqual('localhost:2121', self.types('localhost:2121'))
|
||||
|
||||
# failure scenario
|
||||
def test_missing_port(self):
|
||||
self.assertRaises(ValueError, self.types, '127.0.0.1')
|
||||
|
||||
def test_missing_address(self):
|
||||
self.assertRaises(ValueError, self.types, ':123')
|
||||
|
||||
def test_incorrect_ip(self):
|
||||
self.assertRaises(ValueError, self.types, '127.surprise.0.1:2121')
|
||||
|
||||
def test_incorrect_port(self):
|
||||
self.assertRaises(ValueError, self.types, '127.0.0.1:65536')
|
||||
self.assertRaises(ValueError, self.types, '127.0.0.1:sample')
|
@ -15,13 +15,13 @@
|
||||
import falcon
|
||||
import mock
|
||||
|
||||
from monasca_api import config
|
||||
from monasca_api.healthcheck import base
|
||||
from monasca_api import healthchecks
|
||||
from monasca_api.tests import base as test_base
|
||||
from monasca_api.v2.reference import cfg
|
||||
from monasca_common.rest import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF = config.CONF
|
||||
ENDPOINT = '/healthcheck'
|
||||
|
||||
|
||||
|
@ -16,11 +16,11 @@ import mock
|
||||
|
||||
from monasca_common.kafka_lib import client
|
||||
|
||||
from monasca_api import config
|
||||
from monasca_api.healthcheck import kafka_check as kc
|
||||
from monasca_api.tests import base
|
||||
from monasca_api.v2.reference import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TestKafkaHealthCheckLogic(base.BaseTestCase):
|
||||
|
@ -19,11 +19,11 @@ import requests
|
||||
import mock
|
||||
|
||||
from monasca_api.common.repositories import exceptions
|
||||
from monasca_api import config
|
||||
from monasca_api.healthcheck import metrics_db_check as tdc
|
||||
from monasca_api.tests import base
|
||||
from monasca_api.v2.reference import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TestMetricsDbHealthCheck(base.BaseTestCase):
|
||||
|
@ -1,180 +0,0 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2016 FUJITSU LIMITED
|
||||
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import types
|
||||
from oslo_db import options
|
||||
|
||||
|
||||
"""Configurations for reference implementation
|
||||
|
||||
I think that these configuration parameters should have been split into
|
||||
small groups and be set into each implementation where they get used.
|
||||
|
||||
For example: kafka configuration should have been in the implementation
|
||||
where kafka get used. It seems to me that the configuration for kafka gets
|
||||
used in kafka_publisher, but the original settings were at the api/server.py
|
||||
which I think is at the wrong place. I move these settings here for now, we
|
||||
need to have a bit more re-engineering to get it right.
|
||||
"""
|
||||
global_opts = [cfg.StrOpt('region', help='Region that API is running in'),
|
||||
cfg.ListOpt('valid_notification_periods', default=[0, 60],
|
||||
item_type=int,
|
||||
help='Valid periods for notification methods')
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(global_opts)
|
||||
|
||||
security_opts = [cfg.ListOpt('default_authorized_roles', default=['admin'],
|
||||
help='Roles that are allowed full access to the '
|
||||
'API'),
|
||||
cfg.ListOpt('agent_authorized_roles', default=['agent'],
|
||||
help='Roles that are only allowed to POST to '
|
||||
'the API'),
|
||||
cfg.ListOpt('read_only_authorized_roles', default=['monasca-read-only-user'],
|
||||
help='Roles that are only allowed to GET from '
|
||||
'the API'),
|
||||
cfg.ListOpt('delegate_authorized_roles', default=['admin'],
|
||||
help='Roles that are allowed to POST metrics on '
|
||||
'behalf of another tenant')]
|
||||
|
||||
security_group = cfg.OptGroup(name='security', title='security')
|
||||
cfg.CONF.register_group(security_group)
|
||||
cfg.CONF.register_opts(security_opts, security_group)
|
||||
|
||||
messaging_opts = [cfg.StrOpt('driver', default='kafka',
|
||||
help='The message queue driver to use'),
|
||||
cfg.StrOpt('metrics_message_format', default='reference',
|
||||
help='The type of metrics message format to '
|
||||
'publish to the message queue'),
|
||||
cfg.StrOpt('events_message_format', default='reference',
|
||||
help='The type of events message format to '
|
||||
'publish to the message queue')]
|
||||
|
||||
messaging_group = cfg.OptGroup(name='messaging', title='messaging')
|
||||
cfg.CONF.register_group(messaging_group)
|
||||
cfg.CONF.register_opts(messaging_opts, messaging_group)
|
||||
|
||||
base_sqla_path = 'monasca_api.common.repositories.sqla.'
|
||||
repositories_opts = [
|
||||
cfg.StrOpt('metrics_driver',
|
||||
default='monasca_api.common.repositories.influxdb.metrics_repository:MetricsRepository',
|
||||
help='The repository driver to use for metrics'),
|
||||
cfg.StrOpt('alarm_definitions_driver',
|
||||
default=base_sqla_path + 'alarm_definitions_repository:AlarmDefinitionsRepository',
|
||||
help='The repository driver to use for alarm definitions'),
|
||||
cfg.StrOpt('alarms_driver',
|
||||
default=base_sqla_path + 'alarms_repository:AlarmsRepository',
|
||||
help='The repository driver to use for alarms'),
|
||||
cfg.StrOpt('notifications_driver',
|
||||
default=base_sqla_path + 'notifications_repository:NotificationsRepository',
|
||||
help='The repository driver to use for notifications'),
|
||||
cfg.StrOpt('notification_method_type_driver',
|
||||
default=base_sqla_path + 'notification_method_type_repository:NotificationMethodTypeRepository',
|
||||
help='The repository driver to use for notifications')]
|
||||
|
||||
repositories_group = cfg.OptGroup(name='repositories', title='repositories')
|
||||
cfg.CONF.register_group(repositories_group)
|
||||
cfg.CONF.register_opts(repositories_opts, repositories_group)
|
||||
|
||||
|
||||
kafka_opts = [cfg.StrOpt('uri', help='Address to kafka server. For example: '
|
||||
'uri=192.168.1.191:9092'),
|
||||
cfg.StrOpt('metrics_topic', default='metrics',
|
||||
help='The topic that metrics will be published too.',
|
||||
advanced=True),
|
||||
cfg.StrOpt('events_topic', default='events',
|
||||
help='The topic that events will be published too.',
|
||||
advanced=True),
|
||||
cfg.StrOpt('alarm_state_transitions_topic', default='alarm-state-transitions',
|
||||
help='The topic that alarm state will be published too.',
|
||||
advanced=True),
|
||||
cfg.StrOpt('group', default='api',
|
||||
help='The group name that this service belongs to.'),
|
||||
cfg.IntOpt('wait_time', default=1,
|
||||
help='The wait time when no messages on kafka '
|
||||
'queue.'), cfg.IntOpt('ack_time', default=20,
|
||||
help='The ack time back '
|
||||
'to kafka.'),
|
||||
cfg.IntOpt('max_retry', default=3,
|
||||
help='The number of retry when there is a '
|
||||
'connection error.'),
|
||||
cfg.BoolOpt('auto_commit', default=False,
|
||||
help='If automatically commmit when consume '
|
||||
'messages.'),
|
||||
cfg.BoolOpt('async', default=True, help='The type of posting.'),
|
||||
cfg.BoolOpt('compact', default=True, help=(
|
||||
'Specify if the message received should be parsed.'
|
||||
'If True, message will not be parsed, otherwise '
|
||||
'messages will be parsed.')),
|
||||
cfg.MultiOpt('partitions', item_type=types.Integer(),
|
||||
default=0,
|
||||
help='The partitions this connection should '
|
||||
'listen for messages on. Currently does not '
|
||||
'support multiple partitions. '
|
||||
'Default is to listen on partition 0.'),
|
||||
cfg.BoolOpt('drop_data', default=False, help=(
|
||||
'Specify if received data should be simply dropped. '
|
||||
'This parameter is only for testing purposes.')), ]
|
||||
|
||||
kafka_group = cfg.OptGroup(name='kafka', title='title')
|
||||
cfg.CONF.register_group(kafka_group)
|
||||
cfg.CONF.register_opts(kafka_opts, kafka_group)
|
||||
|
||||
influxdb_opts = [cfg.StrOpt('database_name'), cfg.StrOpt('ip_address'),
|
||||
cfg.StrOpt('port'), cfg.StrOpt('user'),
|
||||
cfg.StrOpt('password', secret=True)]
|
||||
|
||||
influxdb_group = cfg.OptGroup(name='influxdb', title='influxdb')
|
||||
cfg.CONF.register_group(influxdb_group)
|
||||
cfg.CONF.register_opts(influxdb_opts, influxdb_group)
|
||||
|
||||
cassandra_opts = [cfg.StrOpt('cluster_ip_addresses'), cfg.StrOpt('keyspace')]
|
||||
|
||||
cassandra_group = cfg.OptGroup(name='cassandra', title='cassandra')
|
||||
cfg.CONF.register_group(cassandra_group)
|
||||
cfg.CONF.register_opts(cassandra_opts, cassandra_group)
|
||||
|
||||
|
||||
def register_database_opts():
|
||||
# Update the default QueuePool parameters. These can be tweaked by the
|
||||
# conf variables - max_pool_size, max_overflow and pool_timeout
|
||||
|
||||
options.set_defaults(cfg.CONF, connection='sqlite://',
|
||||
max_pool_size=10, max_overflow=20,
|
||||
pool_timeout=10)
|
||||
|
||||
# register old value
|
||||
url_opt = cfg.StrOpt(name='url',
|
||||
default=cfg.CONF.database.connection,
|
||||
required=False,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='1.6.0',
|
||||
deprecated_reason=(
|
||||
'Please use database.connection option,'
|
||||
'database.url is scheduled for removal '
|
||||
'in Pike release')
|
||||
)
|
||||
|
||||
cfg.CONF.register_opts([url_opt], group='database')
|
||||
cfg.CONF.set_override(name='connection', group='database',
|
||||
override=cfg.CONF.database.url)
|
||||
|
||||
register_database_opts()
|
||||
|
||||
|
||||
# support URL as an option till Pike is released
|
||||
# TODO(trebskit) remove in Pike release
|
6
releasenotes/notes/config_gen-ead0282db82e6c0f.yaml
Normal file
6
releasenotes/notes/config_gen-ead0282db82e6c0f.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
other:
|
||||
- |
|
||||
Removed maintaining the example of configuration file. The file was
|
||||
removed from the tree, however it can be generated using oslo.config
|
||||
generator feature. Devstack plugin has been also migrated to use it.
|
@ -24,7 +24,6 @@ packages =
|
||||
|
||||
data_files =
|
||||
/etc/monasca =
|
||||
etc/api-config.conf
|
||||
etc/api-logging.conf
|
||||
etc/api-config.ini
|
||||
|
||||
@ -35,6 +34,9 @@ console_scripts =
|
||||
tempest.test_plugins =
|
||||
monasca_tests = monasca_tempest_tests.plugin:MonascaTempestPlugin
|
||||
|
||||
oslo.config.opts =
|
||||
monasca_api = monasca_api.conf:list_opts
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
build-dir = doc/build
|
||||
@ -63,6 +65,7 @@ universal = 1
|
||||
autodoc_index_modules = True
|
||||
autodoc_exclude_modules =
|
||||
monasca_api.api.wsgi*
|
||||
monasca_api.conf.*
|
||||
monasca_api.tests.*
|
||||
monasca_tempest_tests.*
|
||||
api_doc_dir = contributor/api
|
||||
|
8
tox.ini
8
tox.ini
@ -98,7 +98,7 @@ commands =
|
||||
[testenv:devdocs]
|
||||
description = Builds developer documentation
|
||||
commands =
|
||||
rm -rf doc/build
|
||||
rm -rf doc/build doc/source/contributor/api
|
||||
{[testenv:checkjson]commands}
|
||||
python setup.py build_sphinx
|
||||
|
||||
@ -107,6 +107,8 @@ skip_install = True
|
||||
usedevelop = False
|
||||
description = Validates (pep-like) documenation
|
||||
commands =
|
||||
rm -rf {toxinidir}/doc/source/contributor/api {toxinidir}/doc/build \
|
||||
{toxinidir}/api-ref/build {toxinidir}/releasenotes/build
|
||||
doc8 --file-encoding utf-8 {toxinidir}/doc
|
||||
doc8 --file-encoding utf-8 {toxinidir}/api-ref
|
||||
doc8 --file-encoding utf-8 {toxinidir}/releasenotes
|
||||
@ -123,6 +125,10 @@ commands =
|
||||
bash -c "! find doc/ -type f -name *.json | xargs grep -U -n $'\r'"
|
||||
bash -c '! find doc/ -type f -name *.json | xargs -t -n1 python -m json.tool 2>&1 > /dev/null | grep -B1 -v ^python'
|
||||
|
||||
[testenv:genconfig]
|
||||
description = Generates sample configuration file for monasca-api
|
||||
commands = oslo-config-generator --config-file=config-generator/api-config.conf
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user