From 4031a8b1de65002124ad1353d7c0dad3a86a8132 Mon Sep 17 00:00:00 2001 From: Madhuri Kumari Date: Thu, 16 Mar 2017 11:26:33 +0000 Subject: [PATCH] Added support for oslo.config Currently there was no support for oslo.config in valence. This patch does the following: * Add a config generator tool. * Added support to use oslo.config * Added oslo.i18n support needed for translation Change-Id: I444b47a20a69c3e075ea36abf337f343b38549d0 Partially-Implements: blueprint director-plugin-cookiecutter --- etc/os-config-generator/valence.conf | 3 + etc/valence/valence.conf.sample | 109 +++++++++++++++++---- install_valence.sh | 12 ++- requirements.txt | 2 + setup.cfg | 4 + tox.ini | 4 + valence/api/app.py | 27 +++-- valence/cmd/api.py | 13 +-- valence/common/config.py | 28 ++++++ valence/common/i18n.py | 25 +++++ valence/conf/__init__.py | 25 +++++ valence/conf/api.py | 81 +++++++++++++++ valence/conf/etcd.py | 43 ++++++++ valence/conf/opts.py | 78 +++++++++++++++ valence/conf/podm.py | 63 ++++++++++++ valence/config.py | 78 --------------- valence/db/etcd_db.py | 6 +- valence/db/etcd_driver.py | 8 +- valence/redfish/redfish.py | 15 +-- valence/tests/__init__.py | 9 -- valence/tests/functional/__init__.py | 9 ++ valence/tests/unit/api/test_app.py | 14 ++- valence/tests/unit/api/test_route.py | 9 +- valence/tests/unit/conf/__init__.py | 0 valence/tests/unit/conf/test_conf.py | 72 ++++++++++++++ valence/tests/unit/redfish/test_redfish.py | 18 ++-- valence/version.py | 18 ++++ 27 files changed, 626 insertions(+), 147 deletions(-) create mode 100644 etc/os-config-generator/valence.conf create mode 100644 valence/common/config.py create mode 100644 valence/common/i18n.py create mode 100644 valence/conf/__init__.py create mode 100644 valence/conf/api.py create mode 100644 valence/conf/etcd.py create mode 100644 valence/conf/opts.py create mode 100644 valence/conf/podm.py delete mode 100644 valence/config.py create mode 100644 valence/tests/unit/conf/__init__.py create mode 100644 valence/tests/unit/conf/test_conf.py create mode 100644 valence/version.py diff --git a/etc/os-config-generator/valence.conf b/etc/os-config-generator/valence.conf new file mode 100644 index 0000000..2ff27a9 --- /dev/null +++ b/etc/os-config-generator/valence.conf @@ -0,0 +1,3 @@ +[DEFAULT] +output_file = etc/valence/valence.conf.sample +namespace = valence.conf diff --git a/etc/valence/valence.conf.sample b/etc/valence/valence.conf.sample index 3c15b56..f51ea68 100644 --- a/etc/valence/valence.conf.sample +++ b/etc/valence/valence.conf.sample @@ -1,30 +1,97 @@ [DEFAULT] -#LOG Levels - debug, info, warning, error, critical -log_level= debug -#Server log settings -debug=False -# Log to this file. Make sure the user running rsc has -# permissions to write to this file! -log_file=/var/log/valence/valence.log +[api] -#address and port the server binds too -bind_host = 0.0.0.0 -bind_port = 8181 +# +# From valence.conf +# -#Server request timeout -timeout=1000 +# The port for the valence API server. (port value) +# Minimum value: 0 +# Maximum value: 65535 +#bind_port = 8181 + +# The listen IP for the valence API server. (IP address value) +#bind_host = 127.0.0.1 + +# Enable the integrated stand-alone API to service requests via HTTPS +# instead of HTTP. If there is a front-end service performing HTTPS +# offloading from the service, this option should be False; note, you +# will want to change public API endpoint to represent SSL termination +# URL with 'public_endpoint' option. (boolean value) +#enable_ssl_api = false + +# Number of workers for valence-api service. The default will be the +# number of CPUs available. (integer value) +#workers = 4 + +# The maximum timeout to wait for valence API server to come up. +# (integer value) +#timeout = 1000 + +# The maximum number of items returned in a single response from a +# collection resource. (integer value) +#max_limit = 1000 + +# Configuration file for WSGI definition of API. (string value) +#api_paste_config = api-paste.ini + +# Enable debug mode for valence-api service. (boolean value) +#debug = false + +# The log file location for valence-api service (string value) +#log_file = /var/log/valence/valence.log + +# The granularity of Error log outputs. (string value) +#log_level = debug + +# The log format. (string value) +#log_format = %(asctime)s %(name)-4s %(levelname)-4s %(message)s + + +[etcd] + +# +# From valence.conf +# + +# The port for the etcd server. (port value) +# Minimum value: 0 +# Maximum value: 65535 +#port = 2379 + +# The listen IP for the etcd server. (IP address value) +#host = 127.0.0.1 -#Server workers -workers=4 [podm] -url=https://: -user= -password= -redfish_base_ext=/redfish/v1/ -[database_etcd] -host = localhost -port = 2379 +# +# From valence.conf +# + +# The URL of Redfish API server. (e.g.: http(s)://:/). +# Required. (string value) +#url = + +# User account with admin/server-profile access privilege. Although +# this property is not mandatory it's highly recommended to set a +# username. Optional. (string value) +#username = + +# User account password. Although this property is not mandatory, it's +# highly recommended to set a password. Optional. (string value) +#password = + +# Either a boolean value, a path to a CA_BUNDLE file or directory with +# certificates of trusted CAs. If set to True the driver will verify +# the host certificates; if False the driver will ignore verifying the +# SSL certificate; If it's a path the driver will use the specified +# certificate or one of the certificates in the directory. Defaults to +# True. Optional. (boolean value) +#verify_ca = + +# The URL extension that specifies the Redfish API version that +# valence will interact with (string value) +#base_ext = /redfish/v1/ diff --git a/install_valence.sh b/install_valence.sh index 3f6a80e..d093abd 100755 --- a/install_valence.sh +++ b/install_valence.sh @@ -35,7 +35,17 @@ if [ ! -d "/etc/valence" ]; then mkdir /etc/valence fi chown "$CURR_USER":"$CURR_USER" /etc/valence +VALENCE_CONF=/etc/valence/valence.conf cp etc/valence/valence.conf.sample /etc/valence/valence.conf +sudo sed -i "s/#debug\s*=.*/debug=true/" $VALENCE_CONF +sudo sed -i "s/#log_level\s*=.*/log_level=debug/" $VALENCE_CONF +sudo sed -i "s/#log_file\s*=.*/log_file=/var/log/valence/valence.log/" $VALENCE_CONF +sudo sed -i "s/#bind_host\s*=.*/bind_host=0.0.0.0/" $VALENCE_CONF +sudo sed -i "s/#bind_port\s*=.*/bind_port=8181/" $VALENCE_CONF +sudo sed -i "s/#timeout\s*=.*/timeout=1000/" $VALENCE_CONF +sudo sed -i "s/#workers\s*=.*/workers=4/" $VALENCE_CONF +sudo sed -i "s/#host\s*=.*/host=localhost/" $VALENCE_CONF +sudo sed -i "s/#port\s*=.*/port=2379/" $VALENCE_CONF # create log directory for valence if it doesn't exist if [ ! -d "/var/log/valence" ]; then @@ -87,4 +97,4 @@ if [ $? -ne 0 ]; then fi echo "Installation Completed" -echo "To start valence : sudo service valence start" \ No newline at end of file +echo "To start valence : sudo service valence start" diff --git a/requirements.txt b/requirements.txt index 314c0d7..22a492d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,5 @@ six==1.10.0 gunicorn==19.6.0 python-etcd>=0.4.3 # MIT License oslo.utils>=3.20.0 # Apache-2.0 +oslo.config>=3.22.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 61a9f50..2675a3d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,3 +58,7 @@ source-dir = releasenotes/source console_scripts = valence = valence.cmd.api:main valence-db-manager = valence.cmd.db_manager:main + +oslo.config.opts = + valence = valence.opts:list_opts + valence.conf = valence.conf.opts:list_opts diff --git a/tox.ini b/tox.ini index b313099..445edea 100644 --- a/tox.ini +++ b/tox.ini @@ -72,3 +72,7 @@ show-source = True builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,node_modules import-order-style = pep8 + +[testenv:genconfig] +commands = + oslo-config-generator --config-file etc/os-config-generator/valence.conf --output-file etc/valence/valence.conf.sample diff --git a/valence/api/app.py b/valence/api/app.py index 36d71b7..616c8ac 100644 --- a/valence/api/app.py +++ b/valence/api/app.py @@ -17,27 +17,38 @@ import os.path import flask -import valence.config as cfg +from valence.common import config +import valence.conf +CONF = valence.conf.CONF +PROJECT_NAME = 'valence' _app = None +log_level_map = {'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + 'critical': logging.CRITICAL, + 'notset': logging.NOTSET} + def setup_app(): """Return Flask application""" - app = flask.Flask(cfg.PROJECT_NAME) + config.parse_args(prog=PROJECT_NAME) + app = flask.Flask(PROJECT_NAME) app.url_map.strict_slashes = False TEN_KB = 10 * 1024 # Configure logging - if os.path.isfile(cfg.log_file) and os.access(cfg.log_file, - os.W_OK): + if os.path.isfile(CONF.api.log_file) and os.access(CONF.api.log_file, + os.W_OK): handler = logging.handlers.RotatingFileHandler( - cfg.log_file, maxBytes=TEN_KB, backupCount=1) - handler.setLevel(cfg.log_level) - formatter = logging.Formatter(cfg.log_format) + CONF.api.log_file, maxBytes=TEN_KB, backupCount=1) + handler.setLevel(log_level_map.get(CONF.api.log_level)) + formatter = logging.Formatter(CONF.api.log_format) handler.setFormatter(formatter) app.logger.addHandler(handler) - app.logger.setLevel(cfg.log_level) + app.logger.setLevel(log_level_map.get(CONF.api.log_level)) @app.before_request def before_request_logging(): diff --git a/valence/cmd/api.py b/valence/cmd/api.py index 66dcf0e..2f244e1 100755 --- a/valence/cmd/api.py +++ b/valence/cmd/api.py @@ -19,8 +19,9 @@ import gunicorn.app.base from gunicorn.six import iteritems from valence.api.route import app as application -from valence import config as cfg +import valence.conf +CONF = valence.conf.CONF LOG = logging.getLogger(__name__) @@ -44,13 +45,13 @@ class StandaloneApplication(gunicorn.app.base.BaseApplication): def main(): options = { - 'bind': '%s:%s' % (cfg.bind_host, cfg.bind_port), - 'reload': cfg.debug, - 'timeout': cfg.timeout, - 'workers': cfg.workers + 'bind': '%s:%s' % (CONF.api.bind_host, CONF.api.bind_port), + 'reload': CONF.api.debug, + 'timeout': CONF.api.timeout, + 'workers': CONF.api.workers } LOG.info(("Valence Server on http://%(host)s:%(port)s"), - {'host': cfg.bind_host, 'port': cfg.bind_port}) + {'host': CONF.api.bind_host, 'port': CONF.api.bind_port}) StandaloneApplication(application, options).run() diff --git a/valence/common/config.py b/valence/common/config.py new file mode 100644 index 0000000..c3039c4 --- /dev/null +++ b/valence/common/config.py @@ -0,0 +1,28 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 valence import version + +CONF = cfg.CONF + + +def parse_args(args=None, prog=None): + CONF( + args=args, + project='valence', + prog=prog, + version=version.version_info.version_string(), + ) diff --git a/valence/common/i18n.py b/valence/common/i18n.py new file mode 100644 index 0000000..6fbaa0f --- /dev/null +++ b/valence/common/i18n.py @@ -0,0 +1,25 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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. + +# It's based on oslo.i18n usage in OpenStack Keystone project and +# recommendations from http://docs.openstack.org/developer/oslo.i18n/usage.html + +import oslo_i18n + + +_translators = oslo_i18n.TranslatorFactory(domain='valence') + +# The primary translation function using the well-known name "_" +_ = _translators.primary diff --git a/valence/conf/__init__.py b/valence/conf/__init__.py new file mode 100644 index 0000000..1a850be --- /dev/null +++ b/valence/conf/__init__.py @@ -0,0 +1,25 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 valence.conf import api +from valence.conf import etcd +from valence.conf import podm + +CONF = cfg.CONF + +api.register_opts(CONF) +etcd.register_opts(CONF) +podm.register_opts(CONF) diff --git a/valence/conf/api.py b/valence/conf/api.py new file mode 100644 index 0000000..7923016 --- /dev/null +++ b/valence/conf/api.py @@ -0,0 +1,81 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 valence.common.i18n import _ + +api_service_opts = [ + cfg.PortOpt('bind_port', + default=8181, + help=_('The port for the valence API server.')), + cfg.IPOpt('bind_host', + default='127.0.0.1', + help=_('The listen IP for the valence API server.')), + cfg.BoolOpt('enable_ssl_api', + default=False, + help=_("Enable the integrated stand-alone API to service " + "requests via HTTPS instead of HTTP. If there is a " + "front-end service performing HTTPS offloading from " + "the service, this option should be False; note, you " + "will want to change public API endpoint to represent " + "SSL termination URL with 'public_endpoint' option.")), + cfg.IntOpt('workers', + default=4, + help=_('Number of workers for valence-api service. ' + 'The default will be the number of CPUs available.')), + cfg.IntOpt('timeout', + default=1000, + help=_('The maximum timeout to wait for valence API server ' + 'to come up.')), + cfg.IntOpt('max_limit', + default=1000, + help=_('The maximum number of items returned in a single ' + 'response from a collection resource.')), + cfg.StrOpt('api_paste_config', + default='api-paste.ini', + help=_('Configuration file for WSGI definition of API.')), + cfg.BoolOpt('debug', + default=False, + help=_('Enable debug mode for valence-api service.')) +] + +log_option = [ + cfg.StrOpt('log_file', + default='/var/log/valence/valence.log', + help=_('The log file location for valence-api service')), + cfg.StrOpt('log_level', + default='debug', + help=_('The granularity of Error log outputs.')), + cfg.StrOpt('log_format', + default='%(asctime)s %(name)-4s %(levelname)-4s %(message)s', + help=_('The log format.')), +] + +api_group = cfg.OptGroup(name='api', + title='Options for the valence-api service') + + +ALL_OPTS = (api_service_opts + log_option) + + +def register_opts(conf): + conf.register_group(api_group) + conf.register_opts(ALL_OPTS, api_group) + + +def list_opts(): + return { + api_group: ALL_OPTS + } diff --git a/valence/conf/etcd.py b/valence/conf/etcd.py new file mode 100644 index 0000000..47007e2 --- /dev/null +++ b/valence/conf/etcd.py @@ -0,0 +1,43 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 valence.common.i18n import _ + +etcd_service_opts = [ + cfg.PortOpt('port', + default=2379, + help=_('The port for the etcd server.')), + cfg.IPOpt('host', + default='127.0.0.1', + help=_('The listen IP for the etcd server.')), +] + +etcd_group = cfg.OptGroup(name='etcd', + title='Options for the etcd service') + + +ALL_OPTS = (etcd_service_opts) + + +def register_opts(conf): + conf.register_group(etcd_group) + conf.register_opts(ALL_OPTS, etcd_group) + + +def list_opts(): + return { + etcd_group: ALL_OPTS + } diff --git a/valence/conf/opts.py b/valence/conf/opts.py new file mode 100644 index 0000000..9c0bd75 --- /dev/null +++ b/valence/conf/opts.py @@ -0,0 +1,78 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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. + +""" +This is the single point of entry to generate the sample configuration +file for Zun. It collects all the necessary info from the other modules +in this package. It is assumed that: + +* every other module in this package has a 'list_opts' function which + return a dict where + * the keys are strings which are the group names + * the value of each key is a list of config options for that group +* the valence.conf package doesn't have further packages with config options +* this module is only used in the context of sample file generation +""" + +import collections +import importlib +import os +import pkgutil + +LIST_OPTS_FUNC_NAME = "list_opts" + + +def _tupleize(dct): + """Take the dict of options and convert to the 2-tuple format.""" + return [(key, val) for key, val in dct.items()] + + +def list_opts(): + opts = collections.defaultdict(list) + module_names = _list_module_names() + imported_modules = _import_modules(module_names) + _append_config_options(imported_modules, opts) + return _tupleize(opts) + + +def _list_module_names(): + module_names = [] + package_path = os.path.dirname(os.path.abspath(__file__)) + for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]): + if modname == "opts" or ispkg: + continue + else: + module_names.append(modname) + return module_names + + +def _import_modules(module_names): + imported_modules = [] + for modname in module_names: + mod = importlib.import_module("valence.conf." + modname) + if not hasattr(mod, LIST_OPTS_FUNC_NAME): + msg = "The module 'valence.conf.%s' should have a '%s' "\ + "function which returns the config options." % \ + (modname, LIST_OPTS_FUNC_NAME) + raise AttributeError(msg) + else: + imported_modules.append(mod) + return imported_modules + + +def _append_config_options(imported_modules, config_options): + for mod in imported_modules: + configs = mod.list_opts() + for key, val in configs.items(): + config_options[key].extend(val) diff --git a/valence/conf/podm.py b/valence/conf/podm.py new file mode 100644 index 0000000..785858e --- /dev/null +++ b/valence/conf/podm.py @@ -0,0 +1,63 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 valence.common.i18n import _ + +podm_opts = [ + cfg.StrOpt('url', + help=_('The URL of Redfish API server. (e.g.: ' + 'http(s)://:/). Required.')), + cfg.StrOpt('username', + help=_('User account with admin/server-profile access ' + 'privilege. Although this property is not ' + 'mandatory it\'s highly recommended to set a ' + 'username. Optional.')), + cfg.StrOpt('password', + help=_('User account password. Although this property is ' + 'not mandatory, it\'s highly recommended to set a ' + 'password. Optional.')), + cfg.BoolOpt('verify_ca', + help=_('Either a boolean value, a path to a CA_BUNDLE ' + 'file or directory with certificates of trusted ' + 'CAs. If set to True the driver will verify the ' + 'host certificates; if False the driver will ' + 'ignore verifying the SSL certificate; If it\'s ' + 'a path the driver will use the specified ' + 'certificate or one of the certificates in the ' + 'directory. Defaults to True. Optional.')), + cfg.StrOpt('base_ext', + default='/redfish/v1/', + help=_('The URL extension that specifies the ' + 'Redfish API version that valence will interact with')), +] + + +podm_group = cfg.OptGroup(name='podm', + title='Options for the Refish API service') + + +ALL_OPTS = (podm_opts) + + +def register_opts(conf): + conf.register_group(podm_group) + conf.register_opts(ALL_OPTS, podm_group) + + +def list_opts(): + return { + podm_group: ALL_OPTS + } diff --git a/valence/config.py b/valence/config.py deleted file mode 100644 index a631022..0000000 --- a/valence/config.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# 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. - -"""This Module reads the configuration from .conf file - and set default values if the expected values are not set - -""" - - -import logging - -from six.moves import configparser - - -def get_option(section, key, default, value_type=str): - """Function to support default values - - Though config fallback feature could be used - Py 2.7 doesnt support it - - """ - option_value = config.get( - section, key) if config.has_option( - section, key) else default - if value_type == bool: - return False if str(option_value).lower() == 'false' else True - else: - return value_type(option_value) - -PROJECT_NAME = 'valence' - -config_file = "/etc/%s/%s.conf" % (PROJECT_NAME, PROJECT_NAME) -config = configparser.ConfigParser() -config.read(config_file) - -# Log settings -log_level_map = {'debug': logging.DEBUG, - 'info': logging.INFO, - 'warning': logging.WARNING, - 'error': logging.ERROR, - 'critical': logging.CRITICAL, - 'notset': logging.NOTSET} - -log_default_loc = "/var/log/%s/%s.log" % (PROJECT_NAME, PROJECT_NAME) -log_default_format = "%(asctime)s %(name)-4s %(levelname)-4s %(message)s" -log_level_name = get_option("DEFAULT", "log_level", 'error') - -log_file = get_option("DEFAULT", "log_file", log_default_loc) -log_level = log_level_map.get(log_level_name.lower()) -log_format = get_option("DEFAULT", "log_format", log_default_format) - -# Server Settings -bind_port = get_option("DEFAULT", "bind_port", 8181, int) -bind_host = get_option("DEFAULT", "bind_host", "0.0.0.0") -debug = get_option("DEFAULT", "debug", False, bool) -timeout = get_option("DEFAULT", "timeout", 1000, int) -workers = get_option("DEFAULT", "workers", 4, int) - -# PODM Settings -podm_url = get_option("podm", "url", "http://127.0.0.1") -podm_user = get_option("podm", "user", "admin") -podm_password = get_option("podm", "password", "admin") -redfish_base_ext = get_option("podm", "redfish_base_ext", "/redfish/v1/") - -# Database etcd Settings -etcd_host = get_option("database_etcd", "host", "localhost") -etcd_port = get_option("database_etcd", "port", 2379, int) diff --git a/valence/db/etcd_db.py b/valence/db/etcd_db.py index 078400f..f90d449 100644 --- a/valence/db/etcd_db.py +++ b/valence/db/etcd_db.py @@ -14,17 +14,19 @@ import etcd -from valence import config +import valence.conf from valence.db import models +CONF = valence.conf.CONF + etcd_directories = [ models.PodManager.path, models.Flavor.path, models.ComposedNode.path ] -etcd_client = etcd.Client(config.etcd_host, config.etcd_port) +etcd_client = etcd.Client(CONF.etcd.host, CONF.etcd.port) def init_etcd_db(): diff --git a/valence/db/etcd_driver.py b/valence/db/etcd_driver.py index 79715db..9f0f5e4 100644 --- a/valence/db/etcd_driver.py +++ b/valence/db/etcd_driver.py @@ -21,15 +21,17 @@ from oslo_utils import uuidutils import six from valence.common import singleton -from valence import config +import valence.conf from valence.db import models +CONF = valence.conf.CONF + LOG = logging.getLogger(__name__) def get_driver(): - return EtcdDriver(config.etcd_host, config.etcd_port) + return EtcdDriver(CONF.etcd.host, CONF.etcd.port) def translate_to_models(etcd_resp, model_type): @@ -53,7 +55,7 @@ def translate_to_models(etcd_resp, model_type): class EtcdDriver(object): """etcd Driver.""" - def __init__(self, host=config.etcd_host, port=config.etcd_port): + def __init__(self, host=CONF.etcd.host, port=CONF.etcd.port): self.client = etcd.Client(host=host, port=port) def create_podmanager(self, values): diff --git a/valence/redfish/redfish.py b/valence/redfish/redfish.py index 6a34a6b..39a71a8 100644 --- a/valence/redfish/redfish.py +++ b/valence/redfish/redfish.py @@ -26,23 +26,24 @@ from valence.api import link from valence.common import constants from valence.common import exception from valence.common import utils -from valence import config as cfg +import valence.conf from valence.redfish import tree +CONF = valence.conf.CONF LOG = logging.getLogger(__name__) SERVICE_ROOT = None def update_service_root(): global SERVICE_ROOT - resp = send_request(cfg.redfish_base_ext) + resp = send_request(CONF.podm.base_ext) SERVICE_ROOT = resp.json() def get_rfs_url(serviceext): # Strip slash to make sure all input with/without slash - redfish_base_ext = cfg.redfish_base_ext.strip("/") + redfish_base_ext = CONF.podm.base_ext.strip("/") serviceext = serviceext.strip("/") # Check whether serviceext statswith redfish_base_ext "redfish/v1", if yes, @@ -52,7 +53,7 @@ def get_rfs_url(serviceext): else: relative_url = os.path.normpath( "/".join([redfish_base_ext, serviceext])) - return requests.compat.urljoin(cfg.podm_url, relative_url) + return requests.compat.urljoin(CONF.podm.url, relative_url) def get_base_resource_url(resource, update_services=False): @@ -66,8 +67,8 @@ def get_base_resource_url(resource, update_services=False): def send_request(resource, method="GET", **kwargs): # The verify=false param in the request should be removed eventually url = get_rfs_url(resource) - httpuser = cfg.podm_user - httppwd = cfg.podm_password + httpuser = CONF.podm.username + httppwd = CONF.podm.password resp = None LOG.debug(url) try: @@ -474,7 +475,7 @@ def compose_node(request_body): # Allocated node successfully # node_url -- relative redfish url e.g redfish/v1/Nodes/1 - node_url = allocate_resp.headers['Location'].lstrip(cfg.podm_url) + node_url = allocate_resp.headers['Location'].lstrip(CONF.podm.url) # node_index -- numeric index of new node e.g 1 node_index = node_url.split('/')[-1] LOG.debug('Successfully allocated node:' + node_url) diff --git a/valence/tests/__init__.py b/valence/tests/__init__.py index 36f2a45..e69de29 100644 --- a/valence/tests/__init__.py +++ b/valence/tests/__init__.py @@ -1,9 +0,0 @@ -from unittest import TestCase - -from valence.api.route import app - - -class FunctionalTest(TestCase): - - def setUp(self): - self.app = app.test_client() diff --git a/valence/tests/functional/__init__.py b/valence/tests/functional/__init__.py index e69de29..36f2a45 100644 --- a/valence/tests/functional/__init__.py +++ b/valence/tests/functional/__init__.py @@ -0,0 +1,9 @@ +from unittest import TestCase + +from valence.api.route import app + + +class FunctionalTest(TestCase): + + def setUp(self): + self.app = app.test_client() diff --git a/valence/tests/unit/api/test_app.py b/valence/tests/unit/api/test_app.py index d4d77dc..096841d 100644 --- a/valence/tests/unit/api/test_app.py +++ b/valence/tests/unit/api/test_app.py @@ -17,16 +17,24 @@ import mock from valence.api import app +import valence.conf + +CONF = valence.conf.CONF + class TestApp(unittest.TestCase): - @mock.patch('valence.config.PROJECT_NAME') + @mock.patch('valence.common.config.parse_args') + @mock.patch('valence.api.app.PROJECT_NAME') @mock.patch('valence.api.app.flask.Flask') - def test_setup_app_success(self, mock_Flask, mock_PROJECT_NAME): + def test_setup_app_success(self, mock_Flask, mock_PROJECT_NAME, + mock_parse_arg): + CONF.set_override('log_level', 'debug', group='api') self.app = app.setup_app() mock_Flask.assert_called_with(mock_PROJECT_NAME) + mock_parse_arg.assert_called_once_with(prog=mock_PROJECT_NAME) self.assertFalse(self.app.url_map.strict_slashes) - self.app.logger.setLevel.assert_called_with(app.cfg.log_level) + self.assertTrue(self.app.logger.setLevel.called) @mock.patch('valence.api.app.setup_app') def test_get_app_success(self, mock_setup_app): diff --git a/valence/tests/unit/api/test_route.py b/valence/tests/unit/api/test_route.py index ce9899c..042e6dc 100644 --- a/valence/tests/unit/api/test_route.py +++ b/valence/tests/unit/api/test_route.py @@ -12,18 +12,23 @@ import unittest +import mock + +from valence.common import config +config.parse_args = mock.Mock() + from valence.api import route class TestRoute(unittest.TestCase): def setUp(self): - self.app = route.app + self.app = route.app.test_client() self.api = route.api def test_app(self): self.assertNotEqual(self.app, None) - self.assertEqual(self.app.__class__.__name__, 'Flask') + self.assertEqual(self.app.__class__.__name__, 'FlaskClient') def test_api(self): self.assertNotEqual(self.api, None) diff --git a/valence/tests/unit/conf/__init__.py b/valence/tests/unit/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/valence/tests/unit/conf/test_conf.py b/valence/tests/unit/conf/test_conf.py new file mode 100644 index 0000000..7babec9 --- /dev/null +++ b/valence/tests/unit/conf/test_conf.py @@ -0,0 +1,72 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 collections +import unittest + +import mock + +from oslo_config import cfg +import six + +from valence.conf import opts + + +class ConfTestCase(unittest.TestCase): + + def test_list_opts(self): + for group, opt_list in opts.list_opts(): + if isinstance(group, six.string_types): + self.assertEqual('DEFAULT', group) + else: + self.assertIsInstance(group, cfg.OptGroup) + for opt in opt_list: + self.assertIsInstance(opt, cfg.Opt) + + def test_list_module_name_invalid_mods(self): + with mock.patch('pkgutil.iter_modules') as mock_mods: + mock_mods.return_value = [(None, 'foo', True), + (None, 'opts', False)] + self.assertEqual([], opts._list_module_names()) + + def test_list_module_name_valid_mods(self): + with mock.patch('pkgutil.iter_modules') as mock_mods: + mock_mods.return_value = [(None, 'foo', False)] + self.assertEqual(['foo'], opts._list_module_names()) + + def test_import_mods_no_func(self): + modules = ['foo', 'bar'] + with mock.patch('importlib.import_module') as mock_import: + mock_import.return_value = mock.sentinel.mods + self.assertRaises(AttributeError, opts._import_modules, modules) + mock_import.assert_called_once_with('valence.conf.foo') + + def test_import_mods_valid_func(self): + modules = ['foo', 'bar'] + with mock.patch('importlib.import_module') as mock_import: + mock_mod = mock.MagicMock() + mock_import.return_value = mock_mod + self.assertEqual([mock_mod, mock_mod], + opts._import_modules(modules)) + mock_import.assert_has_calls([mock.call('valence.conf.foo'), + mock.call('valence.conf.bar')]) + + def test_append_config(self): + opt = collections.defaultdict(list) + mock_module = mock.MagicMock() + mock_conf = mock.MagicMock() + mock_module.list_opts.return_value = mock_conf + mock_conf.items.return_value = [('foo', 'bar')] + opts._append_config_options([mock_module], opt) + self.assertEqual({'foo': ['b', 'a', 'r']}, opt) diff --git a/valence/tests/unit/redfish/test_redfish.py b/valence/tests/unit/redfish/test_redfish.py index 38e5bfc..0f14133 100644 --- a/valence/tests/unit/redfish/test_redfish.py +++ b/valence/tests/unit/redfish/test_redfish.py @@ -21,16 +21,18 @@ from six.moves import http_client from valence.common import constants from valence.common import exception -from valence import config as cfg +import valence.conf from valence.redfish import redfish from valence.tests.unit.fakes import redfish_fakes as fakes +CONF = valence.conf.CONF + class TestRedfish(TestCase): def test_get_rfs_url(self): - cfg.podm_url = "https://127.0.0.1:8443" - expected = urljoin(cfg.podm_url, "redfish/v1/Systems/1") + CONF.podm.url = "https://127.0.0.1:8443" + expected = urljoin(CONF.podm.url, "redfish/v1/Systems/1") # test without service_ext result = redfish.get_rfs_url("/Systems/1/") @@ -59,8 +61,8 @@ class TestRedfish(TestCase): self.assertEqual(expected, result) def test_get_rfs_url_with_tailing_slash(self): - cfg.podm_url = "https://127.0.0.1:8443/" - expected = urljoin(cfg.podm_url, "redfish/v1/Systems/1") + CONF.podm.url = "https://127.0.0.1:8443/" + expected = urljoin(CONF.podm.url, "redfish/v1/Systems/1") # test without service_ext result = redfish.get_rfs_url("/Systems/1/") @@ -346,6 +348,7 @@ class TestRedfish(TestCase): def test_assemble_node_failed(self, mock_request, mock_get_url, mock_delete_node): """Test allocate resource conflict when compose node""" + CONF.set_override('url', 'http://localhost:8442/', group='podm') mock_get_url.return_value = '/redfish/v1/Nodes' # Fake response for getting nodes root @@ -355,7 +358,7 @@ class TestRedfish(TestCase): fake_node_allocation_conflict = mock.MagicMock() fake_node_allocation_conflict.status_code = http_client.CREATED fake_node_allocation_conflict.headers['Location'] = \ - os.path.normpath("/".join([cfg.podm_url, 'redfish/v1/Nodes/1'])) + os.path.normpath("/".join([CONF.podm.url, 'redfish/v1/Nodes/1'])) # Fake response for getting url of node assembling fake_node_detail = fakes.mock_request_get(fakes.fake_node_detail(), @@ -381,6 +384,7 @@ class TestRedfish(TestCase): def test_assemble_node_success(self, mock_request, mock_get_url, mock_delete_node, mock_get_node_by_id): """Test compose node successfully""" + CONF.set_override('url', 'http://localhost:8442/', group='podm') mock_get_url.return_value = '/redfish/v1/Nodes' # Fake response for getting nodes root @@ -390,7 +394,7 @@ class TestRedfish(TestCase): fake_node_allocation_conflict = mock.MagicMock() fake_node_allocation_conflict.status_code = http_client.CREATED fake_node_allocation_conflict.headers['Location'] = \ - os.path.normpath("/".join([cfg.podm_url, 'redfish/v1/Nodes/1'])) + os.path.normpath("/".join([CONF.podm.url, 'redfish/v1/Nodes/1'])) # Fake response for getting url of node assembling fake_node_detail = fakes.mock_request_get(fakes.fake_node_detail(), diff --git a/valence/version.py b/valence/version.py new file mode 100644 index 0000000..cda62d7 --- /dev/null +++ b/valence/version.py @@ -0,0 +1,18 @@ +# Copyright (c) 2017 Intel, Inc. +# +# 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 pbr import version + +version_info = version.VersionInfo('valence')