Merge "Added support for oslo.config"

This commit is contained in:
Jenkins 2017-04-04 04:33:40 +00:00 committed by Gerrit Code Review
commit 04fc974ee3
27 changed files with 626 additions and 147 deletions

View File

@ -0,0 +1,3 @@
[DEFAULT]
output_file = etc/valence/valence.conf.sample
namespace = valence.conf

View File

@ -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://<ip address>:<port>
user=<podm user>
password=<podm admin>
redfish_base_ext=/redfish/v1/
[database_etcd]
host = localhost
port = 2379
#
# From valence.conf
#
# The URL of Redfish API server. (e.g.: http(s)://<IP>:<PORT>/).
# Required. (string value)
#url = <None>
# 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 = <None>
# User account password. Although this property is not mandatory, it's
# highly recommended to set a password. Optional. (string value)
#password = <None>
# 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 = <None>
# The URL extension that specifies the Redfish API version that
# valence will interact with (string value)
#base_ext = /redfish/v1/

View File

@ -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

View File

@ -15,3 +15,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

View File

@ -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

View File

@ -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

View File

@ -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,
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():

View File

@ -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()

28
valence/common/config.py Normal file
View File

@ -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(),
)

25
valence/common/i18n.py Normal file
View File

@ -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

25
valence/conf/__init__.py Normal file
View File

@ -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)

81
valence/conf/api.py Normal file
View File

@ -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
}

43
valence/conf/etcd.py Normal file
View File

@ -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
}

78
valence/conf/opts.py Normal file
View File

@ -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)

63
valence/conf/podm.py Normal file
View File

@ -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)://<IP>:<PORT>/). 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
}

View File

@ -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)

View File

@ -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():

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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):

View File

@ -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)

View File

View File

@ -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)

View File

@ -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(),

18
valence/version.py Normal file
View File

@ -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')