From da241b473fa7f2d0b4ca1edd3e0a6a6a7b7540f6 Mon Sep 17 00:00:00 2001 From: Seenivasan Gunabalan Date: Wed, 2 Nov 2016 11:12:30 +0530 Subject: [PATCH] Migrate to flask This commit migrate the valence code to flask and aslo removed rabbitmq. Change-Id: I70234515960e7e2106c5208ced8defc760a4531e --- README.rst | 34 ++-- doc/source/init/valence-controller.conf | 14 -- .../init/{valence-api.conf => valence.conf} | 7 +- etc/valence/valence.conf.sample | 33 +--- install_valence.sh | 31 ++-- requirements.txt | 53 ++---- setup.cfg | 6 +- valence/api/app.py | 64 +++---- valence/api/{controllers => }/base.py | 0 valence/api/config.py | 66 ------- valence/api/controllers/v1/flavor.py | 44 ----- valence/api/controllers/v1/nodes.py | 81 --------- valence/api/controllers/v1/storages.py | 44 ----- valence/api/hooks.py | 14 -- valence/api/{controllers => }/link.py | 13 +- valence/api/{controllers => }/root.py | 64 +++++-- valence/api/route.py | 64 +++++++ valence/api/{controllers => }/types.py | 29 +-- valence/api/{controllers => v1}/__init__.py | 0 .../flavor_controller.py => api/v1/flavor.py} | 24 +-- valence/api/v1/nodes.py | 51 ++++++ .../redfish/config.py => api/v1/storages.py} | 32 ++-- .../{tests/config.py => api/v1/systems.py} | 44 +++-- .../v1/controller.py => v1/version.py} | 80 ++++----- valence/cmd/__init__.py | 0 valence/cmd/api.py | 49 ----- valence/cmd/controller.py | 51 ------ valence/common/__init__.py | 0 valence/common/context.py | 75 -------- valence/common/exceptions.py | 79 --------- valence/common/osinterface.py | 97 ---------- valence/common/redfish/__init__.py | 0 valence/common/rpc.py | 138 --------------- valence/common/rpc_service.py | 89 ---------- valence/config.py | 68 +++++++ valence/controller/__init__.py | 0 valence/controller/api.py | 67 ------- valence/controller/config.py | 65 ------- valence/controller/handlers/__init__.py | 0 .../controller/handlers/node_controller.py | 57 ------ valence/flavor/flavor.py | 33 ++-- valence/flavor/plugins/assettag.py | 4 +- valence/flavor/plugins/default.py | 17 +- valence/flavor/plugins/example.py | 8 +- valence/objects/__init__.py | 0 valence/objects/base.py | 63 ------- .../controllers/v1 => redfish}/__init__.py | 0 .../redfish/api.py => redfish/redfish.py} | 167 +++++++----------- valence/{common => }/redfish/tree.py | 0 valence/run.py | 29 +++ valence/tests/__init__.py | 12 +- valence/tests/functional/test_functional.py | 20 +-- 52 files changed, 539 insertions(+), 1541 deletions(-) delete mode 100755 doc/source/init/valence-controller.conf rename doc/source/init/{valence-api.conf => valence.conf} (56%) rename valence/api/{controllers => }/base.py (100%) delete mode 100644 valence/api/config.py delete mode 100644 valence/api/controllers/v1/flavor.py delete mode 100644 valence/api/controllers/v1/nodes.py delete mode 100644 valence/api/controllers/v1/storages.py delete mode 100644 valence/api/hooks.py rename valence/api/{controllers => }/link.py (83%) rename valence/api/{controllers => }/root.py (50%) create mode 100644 valence/api/route.py rename valence/api/{controllers => }/types.py (76%) rename valence/api/{controllers => v1}/__init__.py (100%) rename valence/{controller/handlers/flavor_controller.py => api/v1/flavor.py} (58%) create mode 100644 valence/api/v1/nodes.py rename valence/{common/redfish/config.py => api/v1/storages.py} (51%) rename valence/{tests/config.py => api/v1/systems.py} (51%) rename valence/api/{controllers/v1/controller.py => v1/version.py} (55%) delete mode 100644 valence/cmd/__init__.py delete mode 100755 valence/cmd/api.py delete mode 100644 valence/cmd/controller.py delete mode 100644 valence/common/__init__.py delete mode 100644 valence/common/context.py delete mode 100644 valence/common/exceptions.py delete mode 100644 valence/common/osinterface.py delete mode 100644 valence/common/redfish/__init__.py delete mode 100644 valence/common/rpc.py delete mode 100644 valence/common/rpc_service.py create mode 100644 valence/config.py delete mode 100644 valence/controller/__init__.py delete mode 100644 valence/controller/api.py delete mode 100644 valence/controller/config.py delete mode 100644 valence/controller/handlers/__init__.py delete mode 100644 valence/controller/handlers/node_controller.py delete mode 100644 valence/objects/__init__.py delete mode 100644 valence/objects/base.py rename valence/{api/controllers/v1 => redfish}/__init__.py (100%) rename valence/{common/redfish/api.py => redfish/redfish.py} (74%) rename valence/{common => }/redfish/tree.py (100%) create mode 100755 valence/run.py diff --git a/README.rst b/README.rst index ae1b081..541c750 100644 --- a/README.rst +++ b/README.rst @@ -16,45 +16,35 @@ Download and Installation The following steps capture how to install valence. All installation steps require super user permissions. -******************** +******************************************* Valence installation -******************** +******************************************* 1. Install software dependencies - ``$ sudo apt-get install git python-pip rabbitmq-server libyaml-0-2 python-dev`` + ``$ sudo apt-get install git python-pip`` - 2. Configure RabbitMq Server + 2. Clone the Valence code from git repo. - ``$ sudo rabbitmqctl add_user valence valence #use this username/pwd in valence.conf`` + ``$ git clone https://git.openstack.org/openstack/rsc`` - ``$ sudo rabbitmqctl set_user_tags valence administrator`` + 3. Install all necessary software pre-requisites using the pip requirements file. - ``$ sudo rabbitmqctl set_permissions valence ".*" ".*" ".*"`` - - 3. Clone the Valence code from git repo and change the directory to root Valence folder. - - 4. Install all necessary software pre-requisites using the pip requirements file. - - ``$ sudo -E pip install -r requirements.txt`` + ``$ pip install -r requirements.txt`` 5. Execute the 'install_valence.sh' file the Valence root directory. - ``$ ./install_valence.sh`` + ``$ sudo bash install_valence.sh`` 6. Check the values in valence.conf located at /etc/valence/valence.conf - ``set the ip/credentials of podm for which this Valence will interact`` + ``set the ip/credentials of podm for which this Valence will interact`` - ``set the rabbitmq user/password to the one given above(Step 2)`` + 7. Check the PYTHON_HOME and other variables in /etc/init/valence.conf - 7. Check the values in /etc/init/valence-api.conf, /etc/init/valence-controller.conf + 8. Start valence service - 8. Start api and controller services - - ``$ sudo service valence-api start`` - - ``$ sudo service valence-controller start`` + ``$ sudo service valence start`` 9. Logs are located at /var/logs/valence/ diff --git a/doc/source/init/valence-controller.conf b/doc/source/init/valence-controller.conf deleted file mode 100755 index df95c57..0000000 --- a/doc/source/init/valence-controller.conf +++ /dev/null @@ -1,14 +0,0 @@ -description "Valence Controller server" - -start on runlevel [2345] -stop on runlevel [!2345] - -env PYTHON_HOME=PYHOME - -exec start-stop-daemon --start --verbose --chuid ${CHUID} \ ---name valence-controller \ ---exec /usr/local/bin/valence-controller -- \ ---log-file=/var/log/valence/valence-controller.log - -respawn - diff --git a/doc/source/init/valence-api.conf b/doc/source/init/valence.conf similarity index 56% rename from doc/source/init/valence-api.conf rename to doc/source/init/valence.conf index 45f0ca2..73e38ad 100644 --- a/doc/source/init/valence-api.conf +++ b/doc/source/init/valence.conf @@ -1,4 +1,4 @@ -description "Valence API server" +description "Valence server" start on runlevel [2345] stop on runlevel [!2345] @@ -7,9 +7,8 @@ env PYTHON_HOME=PYHOME # change the chuid to match yours exec start-stop-daemon --start --verbose --chuid ${CHUID} \ ---name valence-api \ ---exec /usr/local/bin/valence-api -- \ ---log-file=/var/log/valence/valence-api.log +--name valence \ +--exec $PYTHON_HOME/valence -- \ respawn diff --git a/etc/valence/valence.conf.sample b/etc/valence/valence.conf.sample index 27ded30..63527d3 100644 --- a/etc/valence/valence.conf.sample +++ b/etc/valence/valence.conf.sample @@ -1,37 +1,20 @@ [DEFAULT] -# Show more verbose log output (sets INFO log level output) -verbose = True +#LOG Levels - debug, info, warning, error, critical +log_level= debug -# Show debugging output in logs (sets DEBUG log level output) -debug = False - -auth_strategy=noauth +#Server log settings +debug=True # Log to this file. Make sure the user running rsc has # permissions to write to this file! +log_file=/var/log/valence/valence.log - -log_dir=/var/log/valence -rpc_response_timeout = 300 - - -[api] -#address to bind the server to +#address and port the server binds too bind_host = 0.0.0.0 - -# Port the bind the server to bind_port = 8181 -[oslo_messaging_rabbit] -rabbit_host = localhost -rabbit_port = 5672 -rabbit_userid = valence -rabbit_password = valence - [podm] -#url=http://10.223.197.204 url=http:// -user= -password= - +user= +password= diff --git a/install_valence.sh b/install_valence.sh index 6baa627..aaae25b 100755 --- a/install_valence.sh +++ b/install_valence.sh @@ -4,36 +4,30 @@ #author :Intel Corporation #date :17-10-2016 #version :0.1 -#usage :bash install_valence.sh +#usage :sudo -E bash install_valence.sh +#notes :Run this script as sudo user and not as root. +# This script is needed still valence is packaged in to .deb/.rpm #============================================================================== install_log=install_valence.log DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + cd $DIR echo "Current directory: $DIR" >> $install_log if [ "$USER" != 'root' ]; then echo "You must be root to install." exit fi -PYHOME=$(python -c "import site; print site.getsitepackages()[0]") + +PYHOME="/usr/local/bin" echo "Detected PYTHON HOME: $PYHOME" >> $install_log # Copy the config files -cp $DIR/doc/source/init/valence-api.conf /tmp/valence-api.conf -sed -i s/\${CHUID}/$USER/ /tmp/valence-api.conf -#Use alternate sed delimiter because path will -#have / -sed -i "s#PYHOME#$PYHOME#" /tmp/valence-api.conf -mv /tmp/valence-api.conf /etc/init/valence-api.conf -echo "Setting up valence-api config" >> $install_log - -cp $DIR/doc/source/init/valence-controller.conf /tmp/valence-controller.conf -sed -i s/\${CHUID}/$USER/ /tmp/valence-controller.conf -#Use alternate sed delimiter because path will -#have / -sed -i "s#PYHOME#$PYHOME#" /tmp/valence-controller.conf -mv /tmp/valence-controller.conf /etc/init/valence-controller.conf -echo "Setting up valence-controller config" >> $install_log +echo "Setting up valence config" >> $install_log +sed s/\${CHUID}/$USER/ $DIR/doc/source/init/valence.conf > /tmp/valence.conf +#Use alternate sed delimiter because path will have / +sed -i "s#PYHOME#$PYHOME#" /tmp/valence.conf +mv /tmp/valence.conf /etc/init/valence.conf # create conf directory for valence mkdir /etc/valence @@ -52,5 +46,4 @@ if [ $? -ne 0 ]; then fi echo "Installation Completed" -echo "To start api : sudo service valence-api start" -echo "To start controller : sudo service valence-controller start" +echo "To start valence : sudo service valence start" diff --git a/requirements.txt b/requirements.txt index dd3ff3c..09afc05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,41 +1,14 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - pbr>=1.6 -Babel>=2.3.4 -Paste>=2.0.3 -PasteDeploy>=1.5.2 -PyYAML>=3.11 -WebOb>=1.6.1 -amqp<=2.0 -anyjson>=0.3.3 -argparse>=1.2.1 -contextlib2>=0.5.3 -eventlet>=0.19.0 -greenlet>=0.4.10 -kombu>=3.0.35 -logutils>=0.3.3 -monotonic>=1.1 -netaddr>=0.7.18 -netifaces>=0.10.4 -oslo.concurrency>=3.10.0 -oslo.config>=3.11.0 -oslo.context>=2.5.0 -oslo.i18n>=3.7.0 -oslo.log>=3.10.0 -oslo.messaging>=5.4.0 -oslo.middleware>=3.13.0 -oslo.reports>=1.11.0 -oslo.serialization>=2.9.0 -oslo.service>=1.12.0 -oslo.utils>=3.13.0 -oslo.versionedobjects>=1.12.0 -pecan>=1.1.1 -requests>=2.10.0 -six>=1.10.0 -stevedore>=1.15.0 -waitress>=0.9.0 -wrapt>=1.10.8 -wsgiref>=0.1.2 - +aniso8601==1.2.0 +click==6.6 +Flask==0.11.1 +Flask-Cors==3.0.2 +Flask-RESTful==0.3.5 +itsdangerous==0.24 +Jinja2==2.8 +MarkupSafe==0.23 +python-dateutil==2.5.3 +pytz==2016.7 +requests==2.11.1 +six==1.10.0 +Werkzeug==0.11.11 diff --git a/setup.cfg b/setup.cfg index 4899b17..4153086 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,8 +52,4 @@ source-dir = releasenotes/source [entry_points] console_scripts = - valence-api = valence.cmd.api:main - valence-controller = valence.cmd.controller:main - -oslo.config.opts = - valence = valence.api.config:list_opts + valence = valence.run:main diff --git a/valence/api/app.py b/valence/api/app.py index 3d7ec82..6800559 100644 --- a/valence/api/app.py +++ b/valence/api/app.py @@ -10,51 +10,31 @@ # See the License for the specific language governing permissions and # limitations under the License. -from oslo_config import cfg -from oslo_service import service -from pecan import configuration -from pecan import make_app -from valence.api import hooks +from flask import Flask +import logging +from logging.handlers import RotatingFileHandler +from valence import config as cfg + +_app = None -def setup_app(*args, **kwargs): - config = { - 'server': { - 'host': cfg.CONF.api.bind_port, - 'port': cfg.CONF.api.bind_host - }, - 'app': { - 'root': 'valence.api.controllers.root.RootController', - 'modules': ['valence.api'], - 'errors': { - 400: '/error', - '__force_dict__': True - } - } - } - pecan_config = configuration.conf_from_dict(config) +def setup_app(): + """Return Flask application""" + app = Flask(cfg.PROJECT_NAME) + app.url_map.strict_slashes = False - app_hooks = [hooks.CORSHook()] - - app = make_app( - pecan_config.app.root, - hooks=app_hooks, - force_canonical=False, - logging=getattr(config, 'logging', {}) - ) + # Configure logging + handler = RotatingFileHandler(cfg.log_file, maxBytes=10000, backupCount=1) + handler.setLevel(cfg.log_level) + formatter = logging.Formatter(cfg.log_format) + handler.setFormatter(formatter) + app.logger.setLevel(cfg.log_level) + app.logger.addHandler(handler) return app -_launcher = None - - -def serve(api_service, conf, workers=1): - global _launcher - if _launcher: - raise RuntimeError('serve() can only be called once') - - _launcher = service.launch(conf, api_service, workers=workers) - - -def wait(): - _launcher.wait() +def get_app(): + global _app + if not _app: + _app = setup_app() + return _app diff --git a/valence/api/controllers/base.py b/valence/api/base.py similarity index 100% rename from valence/api/controllers/base.py rename to valence/api/base.py diff --git a/valence/api/config.py b/valence/api/config.py deleted file mode 100644 index 8a1551a..0000000 --- a/valence/api/config.py +++ /dev/null @@ -1,66 +0,0 @@ -# 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_log import log as logging -import sys -from valence.common import rpc - -LOG = logging.getLogger(__name__) - -common_opts = [ - cfg.StrOpt('auth_strategy', default='noauth', - help=("The type of authentication to use")), - cfg.BoolOpt('allow_pagination', default=False, - help=("Allow the usage of the pagination")), - cfg.BoolOpt('allow_sorting', default=False, - help=("Allow the usage of the sorting")), - cfg.StrOpt('pagination_max_limit', default="-1", - help=("The maximum number of items returned in a single " - "response, value was 'infinite' or negative integer " - "means no limit")), -] - -api_opts = [ - cfg.StrOpt('bind_host', default='0.0.0.0', - help=("The host IP to bind to")), - cfg.IntOpt('bind_port', default=8181, - help=("The port to bind to")), - cfg.IntOpt('api_workers', default=2, - help=("number of api workers")) -] - - -def init(args, **kwargs): - # Register the configuration options - api_conf_group = cfg.OptGroup(name='api', title='Valence API options') - cfg.CONF.register_group(api_conf_group) - cfg.CONF.register_opts(api_opts, group=api_conf_group) - cfg.CONF.register_opts(common_opts) - logging.register_options(cfg.CONF) - - cfg.CONF(args=args, project='valence', - **kwargs) - - rpc.init(cfg.CONF) - - -def setup_logging(): - """Sets up the logging options for a log with supplied name.""" - product_name = "valence" - logging.setup(cfg.CONF, product_name) - LOG.info("Logging enabled!") - LOG.debug("command line: %s", " ".join(sys.argv)) - - -def list_opts(): - yield None, common_opts diff --git a/valence/api/controllers/v1/flavor.py b/valence/api/controllers/v1/flavor.py deleted file mode 100644 index f4b94f3..0000000 --- a/valence/api/controllers/v1/flavor.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2016 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 oslo_log import log as logging -from pecan import expose -from pecan import request -from valence.controller import api as controller_api - -CONF = cfg.CONF -LOG = logging.getLogger(__name__) - - -class FlavorController(object): - - def __init__(self, *args, **kwargs): - super(FlavorController, self).__init__(*args, **kwargs) - - # HTTP GET /flavor/ - @expose(generic=True, template='json') - def index(self): - LOG.debug("GET /flavor") - rpcapi = controller_api.API(context=request.context) - res = rpcapi.flavor_options() - return res - - # HTTP POST /flavor/ - @index.when(method='POST', template='json') - def index_POST(self, **kw): - LOG.debug("POST /flavor") - rpcapi = controller_api.API(context=request.context) - res = rpcapi.flavor_generate(criteria=kw['criteria']) - return res diff --git a/valence/api/controllers/v1/nodes.py b/valence/api/controllers/v1/nodes.py deleted file mode 100644 index 1216ef1..0000000 --- a/valence/api/controllers/v1/nodes.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2016 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 oslo_log import log as logging -import pecan -from pecan import expose -from pecan import request -from pecan.rest import RestController -from valence.controller import api as controller_api - -CONF = cfg.CONF -LOG = logging.getLogger(__name__) - - -class NodeDetailController(RestController): - def __init__(self, nodeid): - self.nodeid = nodeid - - # HTTP GET /nodes/ - @expose() - def delete(self): - LOG.debug("DELETE /nodes") - rpcapi = controller_api.API(context=request.context) - res = rpcapi.delete_composednode(nodeid=self.nodeid) - LOG.info(str(res)) - return res - - @expose() - def storages(self): - pecan.abort(501, "/nodes/node id/storages") - - -class NodesController(RestController): - - def __init__(self, *args, **kwargs): - super(NodesController, self).__init__(*args, **kwargs) - - # HTTP GET /nodes/ - @expose(template='json') - def get_all(self, **kwargs): - LOG.debug("GET /nodes") - rpcapi = controller_api.API(context=request.context) - res = rpcapi.list_nodes(filters=kwargs) - return res - - # HTTP GET /nodes/ - @expose(template='json') - def post(self, **kwargs): - LOG.debug("POST /nodes") - rpcapi = controller_api.API(context=request.context) - res = rpcapi.compose_nodes(criteria=kwargs) - return res - - @expose(template='json') - def get(self, nodeid): - LOG.debug("GET /nodes" + nodeid) - rpcapi = controller_api.API(context=request.context) - node = rpcapi.get_nodebyid(nodeid=nodeid) - if not node: - pecan.abort(404) - return node - - @expose() - def _lookup(self, nodeid, *remainder): - # node = get_student_by_primary_key(primary_key) - if nodeid: - return NodeDetailController(nodeid), remainder - else: - pecan.abort(404) diff --git a/valence/api/controllers/v1/storages.py b/valence/api/controllers/v1/storages.py deleted file mode 100644 index f75d393..0000000 --- a/valence/api/controllers/v1/storages.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2016 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 oslo_log import log as logging -import pecan -from pecan import expose -from pecan import request -from valence.controller import api as controller_api - -CONF = cfg.CONF -LOG = logging.getLogger(__name__) - - -class StoragesController(object): - - def __init__(self, *args, **kwargs): - super(StoragesController, self).__init__(*args, **kwargs) - - # HTTP GET /storages/ - @expose(generic=True, template='json') - def index(self): - LOG.debug("GET /storages") - rpcapi = controller_api.API(context=request.context) - LOG.debug(rpcapi) - pecan.abort(501, "GET /storages is Not yet implemented") - - @expose(template='json') - def get(self, storageid): - LOG.debug("GET /storages" + storageid) - rpcapi = controller_api.API(context=request.context) - LOG.debug(rpcapi) - pecan.abort(501, "GET /storages/storage is Not yet implemented") diff --git a/valence/api/hooks.py b/valence/api/hooks.py deleted file mode 100644 index b34cf13..0000000 --- a/valence/api/hooks.py +++ /dev/null @@ -1,14 +0,0 @@ -from pecan.hooks import PecanHook - - -class CORSHook(PecanHook): - - def after(self, state): - state.response.headers['Access-Control-Allow-Origin'] = '*' - state.response.headers['Access-Control-Allow-Methods'] = ( - 'GET, POST, DELETE, PUT, LIST, OPTIONS') - state.response.headers['Access-Control-Allow-Headers'] = ( - 'origin, authorization, content-type, accept') - if not state.response.headers['Content-Length']: - state.response.headers['Content-Length'] = ( - str(len(state.response.body))) diff --git a/valence/api/controllers/link.py b/valence/api/link.py similarity index 83% rename from valence/api/controllers/link.py rename to valence/api/link.py index d3bc271..5c8a79b 100644 --- a/valence/api/controllers/link.py +++ b/valence/api/link.py @@ -13,19 +13,16 @@ # under the License. -import pecan -from valence.api.controllers import base -from valence.api.controllers import types +from flask import request +from valence.api import base +from valence.api import types def build_url(resource, resource_args, bookmark=False, base_url=None): if base_url is None: - base_url = pecan.request.host_url - + base_url = request.root_url + base_url = base_url.rstrip("//") template = '%(url)s/%(res)s' if bookmark else '%(url)s/v1/%(res)s' - # FIXME(lucasagomes): I'm getting a 404 when doing a GET on - # a nested resource that the URL ends with a '/'. - # https://groups.google.com/forum/#!topic/pecan-dev/QfSeviLg5qs template += '%(args)s' if resource_args.startswith('?') else '/%(args)s' return template % {'url': base_url, 'res': resource, 'args': resource_args} diff --git a/valence/api/controllers/root.py b/valence/api/root.py similarity index 50% rename from valence/api/controllers/root.py rename to valence/api/root.py index b5136c8..1d20461 100644 --- a/valence/api/controllers/root.py +++ b/valence/api/root.py @@ -13,13 +13,14 @@ # under the License. -from pecan import expose -from pecan import request -from pecan import route -from valence.api.controllers import base -from valence.api.controllers import link -from valence.api.controllers import types -from valence.api.controllers.v1 import controller as v1controller +from flask import abort +from flask import request +from flask_restful import Resource +import json +from valence.api import base +from valence.api import link +from valence.api import types +from valence.redfish import redfish as rfs class Version(base.APIBase): @@ -32,18 +33,26 @@ class Version(base.APIBase): 'links': { 'validate': types.List(types.Custom(link.Link)).validate }, + 'min_version': { + 'validate': types.Text.validate + }, + 'status': { + 'validate': types.Text.validate + }, } @staticmethod - def convert(id): + def convert(id, min_version, current=False): version = Version() version.id = id - version.links = [link.Link.make_link('self', request.host_url, + version.status = "CURRENT" if current else "DEPRECTED" + version.min_version = min_version + version.links = [link.Link.make_link('self', request.url_root, id, '', bookmark=True)] return version -class Root(base.APIBase): +class RootBase(base.APIBase): fields = { 'id': { @@ -62,17 +71,34 @@ class Root(base.APIBase): @staticmethod def convert(): - root = Root() + root = RootBase() root.name = "OpenStack Valence API" - root.description = ("Valence is an OpenStack project") - root.versions = [Version.convert('v1')] - root.default_version = Version.convert('v1') + root.description = "Valence is an OpenStack project" + root.versions = [Version.convert('v1', '1.0', True)] + root.default_version = Version.convert('v1', '1.0', True) return root -class RootController(object): - @expose('json') - def index(self): - return Root.convert() +class Root(Resource): -route(RootController, 'v1', v1controller.V1Controller()) + def get(self): + obj = RootBase.convert() + return json.dumps(obj, default=lambda o: o.as_dict()) + + +class PODMProxy(Resource): + """Passthrough Proxy for PODM. + + This function byepasses valence processing + and calls PODM directly. This function may be temperory + + """ + def get(self, url): + op = url.split("/")[0] + filterext = ["Chassis", "Services", "Managers", "Systems", + "EventService", "Nodes", "EthernetSwitches"] + if op in filterext: + resp = rfs.send_request(url) + return resp.json() + else: + abort(404) diff --git a/valence/api/route.py b/valence/api/route.py new file mode 100644 index 0000000..213276c --- /dev/null +++ b/valence/api/route.py @@ -0,0 +1,64 @@ +# Copyright (c) 2016 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 flask_cors import CORS +from flask_restful import Api +from valence.api import app as flaskapp +from valence.api.root import PODMProxy +from valence.api.root import Root +from valence.api.v1.flavor import Flavors as v1Flavors +from valence.api.v1.nodes import Nodes as v1Nodes +from valence.api.v1.nodes import NodesList as v1NodesList +from valence.api.v1.nodes import NodesStorage as v1NodesStorage +from valence.api.v1.storages import Storages as v1Storages +from valence.api.v1.storages import StoragesList as v1StoragesList +from valence.api.v1.systems import Systems as v1Systems +from valence.api.v1.systems import SystemsList as v1SystemsList +from valence.api.v1.version import V1 + +app = flaskapp.get_app() +cors = CORS(app) +api = Api(app) + +"""API V1.0 Operations""" + + +# API Root operation +api.add_resource(Root, '/', endpoint='root') + +# V1 Root operations +api.add_resource(V1, '/v1', endpoint='v1') + +# Node(s) operations +api.add_resource(v1NodesList, '/v1/nodes', endpoint='nodes') +api.add_resource(v1Nodes, '/v1/nodes/', endpoint='node') +api.add_resource(v1NodesStorage, + '/v1/nodes//storages', + endpoint='nodes_storages') + +# System(s) operations +api.add_resource(v1SystemsList, '/v1/systems', endpoint='systems') +api.add_resource(v1Systems, '/v1/systems/', endpoint='system') + +# Flavor(s) operations +api.add_resource(v1Flavors, '/v1/flavor', endpoint='flavor') + + +# Storage(s) operations +api.add_resource(v1StoragesList, '/v1/storages', endpoint='storages') +api.add_resource(v1Storages, + '/v1/storages/', endpoint='storage') + +# Proxy to PODM +api.add_resource(PODMProxy, '/', endpoint='podmproxy') diff --git a/valence/api/controllers/types.py b/valence/api/types.py similarity index 76% rename from valence/api/controllers/types.py rename to valence/api/types.py index f9e819e..411a205 100644 --- a/valence/api/controllers/types.py +++ b/valence/api/types.py @@ -11,9 +11,7 @@ # under the License. import logging -from oslo_utils import strutils import six -from valence.common import exceptions as exception LOG = logging.getLogger(__name__) @@ -27,7 +25,7 @@ class Text(object): return None if not isinstance(value, six.string_types): - raise exception.InvalidValue(value=value, type=cls.type_name) + raise ValueError("An invalid value was provided") return value @@ -41,12 +39,15 @@ class String(object): return None try: - strutils.check_string_length(value, min_length=min_length, - max_length=max_length) + strlen = len(value) + if strlen < min_length: + raise TypeError('String length is less than' + min_length) + if max_length and strlen > max_length: + raise TypeError('String length is greater than' + max_length) except TypeError: - raise exception.InvalidValue(value=value, type=cls.type_name) + raise ValueError("An invalid value was provided") except ValueError as e: - raise exception.InvalidValue(message=str(e)) + raise ValueError(str(e)) return value @@ -64,12 +65,12 @@ class Integer(object): value = int(value) except Exception: LOG.exception('Failed to convert value to int') - raise exception.InvalidValue(value=value, type=cls.type_name) + raise ValueError("Failed to convert value to int") if minimum is not None and value < minimum: message = _("Integer '%(value)s' is smaller than " "'%(min)d'.") % {'value': value, 'min': minimum} - raise exception.InvalidValue(message=message) + raise ValueError(message) return value @@ -84,10 +85,10 @@ class Bool(object): if not isinstance(value, bool): try: - value = strutils.bool_from_string(value, strict=True) + value = value.lower() in ("yes", "true", "t", "1") except Exception: LOG.exception('Failed to convert value to bool') - raise exception.InvalidValue(value=value, type=cls.type_name) + raise ValueError("Failed to convert value to bool") return value @@ -107,7 +108,7 @@ class Custom(object): value = self.user_class(**value) except Exception: LOG.exception('Failed to validate received value') - raise exception.InvalidValue(value=value, type=self.type_name) + raise ValueError("Failed to validate received value") return value @@ -123,10 +124,10 @@ class List(object): return None if not isinstance(value, list): - raise exception.InvalidValue(value=value, type=self.type_name) + raise ValueError("Failed to validate received value") try: return [self.type.validate(v) for v in value] except Exception: LOG.exception('Failed to validate received value') - raise exception.InvalidValue(value=value, type=self.type_name) + raise ValueError("Failed to validate received value") diff --git a/valence/api/controllers/__init__.py b/valence/api/v1/__init__.py similarity index 100% rename from valence/api/controllers/__init__.py rename to valence/api/v1/__init__.py diff --git a/valence/controller/handlers/flavor_controller.py b/valence/api/v1/flavor.py similarity index 58% rename from valence/controller/handlers/flavor_controller.py rename to valence/api/v1/flavor.py index f4195b0..a1aba79 100644 --- a/valence/controller/handlers/flavor_controller.py +++ b/valence/api/v1/flavor.py @@ -12,26 +12,20 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_log import log as logging +from flask import request +from flask_restful import Resource +import logging from valence.flavor import flavor LOG = logging.getLogger(__name__) -class Handler(object): - """Valence Flavor RPC handler. +class Flavors(Resource): - These are the backend operations. They are executed by the backend ervice. - API calls via AMQP (within the ReST API) trigger the handlers to be called. - - """ - - def __init__(self): - super(Handler, self).__init__() - - def flavor_options(self, context): + def get(self): + LOG.debug("GET /flavor") return flavor.get_available_criteria() - def flavor_generate(self, context, criteria): - LOG.debug("Getting flavor options") - return flavor.create_flavors(criteria) + def post(self): + LOG.debug("POST /flavor") + return flavor.create_flavors(request.get_json()) diff --git a/valence/api/v1/nodes.py b/valence/api/v1/nodes.py new file mode 100644 index 0000000..78a111a --- /dev/null +++ b/valence/api/v1/nodes.py @@ -0,0 +1,51 @@ +# Copyright (c) 2016 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 flask import request +from flask_restful import abort +from flask_restful import Resource +import logging +from valence.redfish import redfish as rfs + +LOG = logging.getLogger(__name__) + + +class NodesList(Resource): + + def get(self): + LOG.debug("GET /nodes") + return rfs.nodes_list(request.args) + + def post(self): + LOG.debug("POST /nodes/") + return rfs.compose_node(request.get_json()) + + +class Nodes(Resource): + + def get(self, nodeid): + LOG.debug("GET /nodes/" + nodeid) + return rfs.get_nodebyid(nodeid) + + def delete(self, nodeid): + LOG.debug("DELETE /nodes/" + nodeid) + return rfs.delete_composednode(nodeid) + + +class NodesStorage(Resource): + + def get(self, nodeid): + LOG.debug("GET /nodes/%s/storage" % nodeid) + return abort(501) diff --git a/valence/common/redfish/config.py b/valence/api/v1/storages.py similarity index 51% rename from valence/common/redfish/config.py rename to valence/api/v1/storages.py index 0b526dd..e0a9851 100644 --- a/valence/common/redfish/config.py +++ b/valence/api/v1/storages.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright (c) 2016 Intel, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,21 +12,22 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_config import cfg +from flask_restful import abort +from flask_restful import Resource +import logging + +LOG = logging.getLogger(__name__) -# Configurations -podm_opts = [ - cfg.StrOpt('url', - default='http://localhost:80', - help=("The complete url string of PODM")), - cfg.StrOpt('user', - default='admin', - help=("User for the PODM")), - cfg.StrOpt('password', - default='admin', - help=("Passoword for PODM"))] +class StoragesList(Resource): -podm_conf_group = cfg.OptGroup(name='podm', title='RSC PODM options') -cfg.CONF.register_group(podm_conf_group) -cfg.CONF.register_opts(podm_opts, group=podm_conf_group) + def get(self): + LOG.debug("GET /storages") + return abort(501) + + +class Storages(Resource): + + def get(self, storageid): + LOG.debug("GET /storages" + storageid) + return abort(501) diff --git a/valence/tests/config.py b/valence/api/v1/systems.py similarity index 51% rename from valence/tests/config.py rename to valence/api/v1/systems.py index 80f0933..9bb6406 100644 --- a/valence/tests/config.py +++ b/valence/api/v1/systems.py @@ -1,3 +1,5 @@ +# Copyright (c) 2016 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 @@ -10,28 +12,24 @@ # License for the specific language governing permissions and limitations # under the License. -# Server Specific Configurations -server = { - 'port': '8080', - 'host': '0.0.0.0' -} -# Pecan Application Configurations -app = { - 'root': 'valence.controllers.root.RootController', - 'modules': ['valence'], - 'static_root': '%(confdir)s/../../public', - 'template_path': '%(confdir)s/../templates', - 'debug': True, - 'errors': { - '404': '/error/404', - '__force_dict__': True - } -} +from flask import request +from flask_restful import Resource +import logging +from valence.redfish import redfish as rfs -# Custom Configurations must be in Python dictionary format:: -# -# foo = {'bar':'baz'} -# -# All configurations are accessible at:: -# pecan.conf +LOG = logging.getLogger(__name__) + + +class SystemsList(Resource): + + def get(self): + LOG.debug("GET /systems") + return rfs.systems_list(request.args) + + +class Systems(Resource): + + def get(self, systemid): + LOG.debug("GET /systems/" + systemid) + return rfs.get_systembyid(systemid) diff --git a/valence/api/controllers/v1/controller.py b/valence/api/v1/version.py similarity index 55% rename from valence/api/controllers/v1/controller.py rename to valence/api/v1/version.py index 0eea33f..ec7524d 100644 --- a/valence/api/controllers/v1/controller.py +++ b/valence/api/v1/version.py @@ -12,16 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. -from pecan import abort -from pecan import expose -from pecan import request -from pecan import route -from valence.api.controllers import base -from valence.api.controllers import link -from valence.api.controllers import types -from valence.api.controllers.v1 import flavor as v1flavor -from valence.api.controllers.v1 import nodes as v1nodes -from valence.common.redfish import api as rfsapi + +from flask import request +from flask_restful import Resource +import json +from valence.api import base +from valence.api import link +from valence.api import types class MediaType(base.APIBase): @@ -37,7 +34,7 @@ class MediaType(base.APIBase): } -class V1(base.APIBase): +class V1Base(base.APIBase): """The representation of the version 1 of the API.""" fields = { @@ -50,16 +47,23 @@ class V1(base.APIBase): 'links': { 'validate': types.List(types.Custom(link.Link)).validate }, - 'services': { + 'nodes': { + 'validate': types.List(types.Custom(link.Link)).validate + }, + 'storages': { + 'validate': types.List(types.Custom(link.Link)).validate + }, + 'flavors': { 'validate': types.List(types.Custom(link.Link)).validate }, } @staticmethod def convert(): - v1 = V1() + v1 = V1Base() v1.id = "v1" - v1.links = [link.Link.make_link('self', request.host_url, + v1_base_url = request.url_root.rstrip('//') + v1.links = [link.Link.make_link('self', request.url_root, 'v1', '', bookmark=True), link.Link.make_link('describedby', 'http://docs.openstack.org', @@ -68,37 +72,29 @@ class V1(base.APIBase): bookmark=True, type='text/html')] v1.media_types = [MediaType(base='application/json', type='application/vnd.openstack.valence.v1+json')] - v1.services = [link.Link.make_link('self', request.host_url, - 'services', ''), + v1.nodes = [link.Link.make_link('self', v1_base_url + '/nodes', + 'nodes', ''), + link.Link.make_link('bookmark', + v1_base_url + '/nodes', + 'nodes', '', + bookmark=True)] + v1.storages = [link.Link.make_link('self', v1_base_url, + 'storages', ''), link.Link.make_link('bookmark', - request.host_url, - 'services', '', + v1_base_url, + 'storages', '', bookmark=True)] + v1.flavors = [link.Link.make_link('self', v1_base_url, + 'flavors', ''), + link.Link.make_link('bookmark', + v1_base_url, + 'flavors', '', + bookmark=True)] return v1 -class V1Controller(object): - @expose('json') - def index(self): - return V1.convert() +class V1(Resource): - @expose('json') - def _default(self, *args): - """Passthrough Proxy for PODM. - - This function byepasses valence controller handlers - and calls PODM directly. - - """ - ext = args[0] - filterext = ["Chassis", "Services", "Managers", "Systems", - "EventService", "Nodes", "EthernetSwitches"] - if ext in filterext: - urlext = '/'.join(args) - resp = rfsapi.send_request(urlext) - return resp.json() - else: - abort(404) - -route(V1Controller, 'flavor', v1flavor.FlavorController()) -route(V1Controller, 'nodes', v1nodes.NodesController()) + def get(self): + vobj = V1Base.convert() + return json.dumps(vobj, default=lambda o: o.as_dict()) diff --git a/valence/cmd/__init__.py b/valence/cmd/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/valence/cmd/api.py b/valence/cmd/api.py deleted file mode 100755 index 7841006..0000000 --- a/valence/cmd/api.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python - -# copyright (c) 2016 Intel, Inc. -# -# 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 as logging -from oslo_service import wsgi - -from valence.api import app -from valence.api import config as api_config - -CONF = cfg.CONF -LOG = logging.getLogger('valence.api') - - -def main(): - api_config.init(sys.argv[1:]) - api_config.setup_logging() - application = app.setup_app() - host = CONF.api.bind_host - port = CONF.api.bind_port - workers = 1 - - LOG.info(("Server on http://%(host)s:%(port)s with %(workers)s"), - {'host': host, 'port': port, 'workers': workers}) - - service = wsgi.Server(CONF, "valence", application, host, port) - - app.serve(service, CONF, workers) - - LOG.info("Configuration:") - app.wait() - - -if __name__ == '__main__': - main() diff --git a/valence/cmd/controller.py b/valence/cmd/controller.py deleted file mode 100644 index db48d15..0000000 --- a/valence/cmd/controller.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2016 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. - -"""Starter script for the Valence controller service.""" - -import os -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import service -import sys -import uuid -from valence.common import rpc_service -from valence.controller import config as controller_config -from valence.controller.handlers import flavor_controller -from valence.controller.handlers import node_controller - -LOG = logging.getLogger(__name__) - - -def main(): - controller_config.init(sys.argv[1:]) - controller_config.setup_logging() - LOG.info(('Starting valence-controller in PID %s'), os.getpid()) - LOG.debug("Configuration:") - controller_id = uuid.uuid4() - endpoints = [ - flavor_controller.Handler(), - node_controller.Handler() - ] - - server = rpc_service.Service.create(cfg.CONF.controller.topic, - controller_id, endpoints, - binary='valence-controller') - launcher = service.launch(cfg.CONF, server) - launcher.wait() - -if __name__ == '__main__': - main() diff --git a/valence/common/__init__.py b/valence/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/valence/common/context.py b/valence/common/context.py deleted file mode 100644 index c0d7641..0000000 --- a/valence/common/context.py +++ /dev/null @@ -1,75 +0,0 @@ -# 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_context import context as oslo_ctx - - -class ContextBase(oslo_ctx.RequestContext): - def __init__(self, auth_token=None, user_id=None, tenant_id=None, - is_admin=False, request_id=None, overwrite=True, - user_name=None, tenant_name=None, auth_url=None, - region=None, password=None, domain='default', - project_name=None, **kwargs): - super(ContextBase, self).__init__( - auth_token=auth_token, - user=user_id or kwargs.get('user', None), - tenant=tenant_id or kwargs.get('tenant', None), - domain=kwargs.get('domain', None), - user_domain=kwargs.get('user_domain', None), - project_domain=kwargs.get('project_domain', None), - is_admin=is_admin, - read_only=kwargs.get('read_only', False), - show_deleted=kwargs.get('show_deleted', False), - request_id=request_id, - resource_uuid=kwargs.get('resource_uuid', None), - overwrite=overwrite) - self.user_name = user_name - self.tenant_name = tenant_name - self.tenant_id = tenant_id - self.auth_url = auth_url - self.password = password - self.default_name = domain - self.region_name = region - self.project_name = project_name - - def to_dict(self): - ctx_dict = super(ContextBase, self).to_dict() - # ctx_dict.update({ - # to do : dict update - # }) - return ctx_dict - - @classmethod - def from_dict(cls, ctx): - return cls(**ctx) - - -class Context(ContextBase): - def __init__(self, **kwargs): - super(Context, self).__init__(**kwargs) - self._session = None - - @property - def session(self): - return self._session - - -def get_admin_context(read_only=True): - return ContextBase(user_id=None, - project_id=None, - is_admin=True, - overwrite=False, - read_only=read_only) - - -def get_current(): - return oslo_ctx.get_current() diff --git a/valence/common/exceptions.py b/valence/common/exceptions.py deleted file mode 100644 index f195416..0000000 --- a/valence/common/exceptions.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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. - -""" -RSC base exception handling. -""" -import six - -from oslo_utils import excutils - - -class RSCException(Exception): - """Base RSC Exception.""" - - message = "An unknown exception occurred." - - def __init__(self, **kwargs): - try: - super(RSCException, self).__init__(self.message % kwargs) - self.msg = self.message % kwargs - except Exception: - with excutils.save_and_reraise_exception() as ctxt: - if not self.use_fatal_exceptions(): - ctxt.reraise = False - # at least get the core message out if something happened - super(RSCException, self).__init__(self.message) - - if six.PY2: - def __unicode__(self): - return unicode(self.msg) - - def use_fatal_exceptions(self): - return False - - -class BadRequest(RSCException): - message = 'Bad %(resource)s request' - - -class NotImplemented(RSCException): - message = ("Not yet implemented in RSC %(func_name)s: ") - - -class NotFound(RSCException): - message = ("URL not Found") - - -class Conflict(RSCException): - pass - - -class ServiceUnavailable(RSCException): - message = "The service is unavailable" - - -class ConnectionRefused(RSCException): - message = "Connection to the service endpoint is refused" - - -class TimeOut(RSCException): - message = "Timeout when connecting to OpenStack Service" - - -class InternalError(RSCException): - message = "Error when performing operation" - - -class InvalidInputError(RSCException): - message = ("An invalid value was provided for %(opt_name)s: " - "%(opt_value)s") diff --git a/valence/common/osinterface.py b/valence/common/osinterface.py deleted file mode 100644 index 228c635..0000000 --- a/valence/common/osinterface.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2016 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 json -from oslo_config import cfg -from oslo_log import log as logging -import requests -from requests.auth import HTTPBasicAuth - - -LOG = logging.getLogger(__name__) -cfg.CONF.import_group('undercloud', 'valence.controller.config') - - -def _send_request(url, method, headers, requestbody=None): - defaultheaders = {'Content-Type': 'application/json'} - auth = HTTPBasicAuth(cfg.CONF.undercloud.os_user, - cfg.CONF.undercloud.os_password) - headers = defaultheaders.update(headers) - LOG.debug(url) - resp = requests.request(method, - url, - headers=defaultheaders, - data=requestbody, - auth=auth) - LOG.debug(resp.status_code) - return resp.json() - - -def _get_servicecatalogue_endpoint(keystonejson, servicename): - """Fetch particular endpoint from Keystone. - - This function is to get the particular endpoint from the - list of endpoints returned fro keystone. - - """ - - for d in keystonejson["access"]["serviceCatalog"]: - if(d["name"] == servicename): - return d["endpoints"][0]["publicURL"] - - -def _get_token_and_url(nameofendpoint): - """Fetch token from the endpoint - - This function get new token and associated endpoint. - name of endpoint carries the name of the service whose - endpoint need to be found. - - """ - - url = cfg.CONF.undercloud.os_admin_url + "/tokens" - data = {"auth": - {"tenantName": cfg.CONF.undercloud.os_tenant, - "passwordCredentials": - {"username": cfg.CONF.undercloud.os_user, - "password": cfg.CONF.undercloud.os_password}}} - rdata = _send_request(url, "POST", {}, json.dumps(data)) - tokenid = rdata["access"]["token"]["id"] - endpoint = _get_servicecatalogue_endpoint(rdata, nameofendpoint) - LOG.debug("Token,Endpoint %s: %s from keystone for %s" - % (tokenid, endpoint, nameofendpoint)) - return (tokenid, endpoint) - - -# put this function in utils.py later -def _get_imageid(jsondata, imgname): - # write a generic funciton for this and _get_servicecatalogue_endpoint - for d in jsondata["images"]: - if(d["name"] == imgname): - return d["id"] - - -def get_undercloud_images(): - tokenid, endpoint = _get_token_and_url("glance") - resp = _send_request(endpoint + "/v2/images", - "GET", - {'X-Auth-Token': tokenid}) - imagemap = {"deploy_ramdisk": _get_imageid(resp, "bm-deploy-ramdisk"), - "deploy_kernel": _get_imageid(resp, "bm-deploy-kernel"), - "image_source": _get_imageid(resp, "overcloud-full"), - "ramdisk": _get_imageid(resp, "overcloud-full-initrd"), - "kernel": _get_imageid(resp, "overcloud-full-vmlinuz")} - return imagemap diff --git a/valence/common/redfish/__init__.py b/valence/common/redfish/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/valence/common/rpc.py b/valence/common/rpc.py deleted file mode 100644 index 4026294..0000000 --- a/valence/common/rpc.py +++ /dev/null @@ -1,138 +0,0 @@ -# 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 oslo_messaging as messaging -# from oslo_serialization import jsonutils -# from valence.common import valencecontext -from oslo_config import cfg -import oslo_messaging as messaging -from oslo_serialization import jsonutils -from valence.common import context as valence_ctx -import valence.common.exceptions - - -__all__ = [ - 'init', - 'cleanup', - 'set_defaults', - 'add_extra_exmods', - 'clear_extra_exmods', - 'get_allowed_exmods', - 'RequestContextSerializer', - 'get_client', - 'get_server', - 'get_notifier', -] - -CONF = cfg.CONF -TRANSPORT = None -NOTIFIER = None - -ALLOWED_EXMODS = [ - valence.common.exceptions.__name__, -] -EXTRA_EXMODS = [] - - -def init(conf): - global TRANSPORT, NOTIFIER - exmods = get_allowed_exmods() - TRANSPORT = messaging.get_transport(conf, - allowed_remote_exmods=exmods) - serializer = RequestContextSerializer(JsonPayloadSerializer()) - NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer) - - -def cleanup(): - global TRANSPORT, NOTIFIER - assert TRANSPORT is not None - assert NOTIFIER is not None - TRANSPORT.cleanup() - TRANSPORT = NOTIFIER = None - - -def set_defaults(control_exchange): - messaging.set_transport_defaults(control_exchange) - - -def add_extra_exmods(*args): - EXTRA_EXMODS.extend(args) - - -def clear_extra_exmods(): - del EXTRA_EXMODS[:] - - -def get_allowed_exmods(): - return ALLOWED_EXMODS + EXTRA_EXMODS - - -class JsonPayloadSerializer(messaging.NoOpSerializer): - @staticmethod - def serialize_entity(context, entity): - return jsonutils.to_primitive(entity, convert_instances=True) - - -class RequestContextSerializer(messaging.Serializer): - - def __init__(self, base): - self._base = base - - def serialize_entity(self, context, entity): - if not self._base: - return entity - return self._base.serialize_entity(context, entity) - - def deserialize_entity(self, context, entity): - if not self._base: - return entity - return self._base.deserialize_entity(context, entity) - - def serialize_context(self, context): - if isinstance(context, dict): - return context - else: - return context.to_dict() - - def deserialize_context(self, context): - return valence_ctx.Context.from_dict(context) - - -def get_transport_url(url_str=None): - return messaging.TransportURL.parse(CONF, url_str) - - -def get_client(target, version_cap=None, serializer=None): - assert TRANSPORT is not None - serializer = RequestContextSerializer(serializer) - return messaging.RPCClient(TRANSPORT, - target, - version_cap=version_cap, - serializer=serializer) - - -def get_server(target, endpoints, serializer=None): - assert TRANSPORT is not None - serializer = RequestContextSerializer(serializer) - return messaging.get_rpc_server(TRANSPORT, - target, - endpoints, - executor='eventlet', - serializer=serializer) - - -def get_notifier(service, host=None, publisher_id=None): - assert NOTIFIER is not None - if not publisher_id: - publisher_id = "%s.%s" % (service, host or CONF.host) - return NOTIFIER.prepare(publisher_id=publisher_id) diff --git a/valence/common/rpc_service.py b/valence/common/rpc_service.py deleted file mode 100644 index ed7f30a..0000000 --- a/valence/common/rpc_service.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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. - -"""Common RPC service and API tools for Valence.""" - -import eventlet -from oslo_config import cfg -import oslo_messaging as messaging -from oslo_service import service - -from valence.common import rpc -from valence.objects import base as objects_base - -eventlet.monkey_patch() - -periodic_opts = [ - cfg.IntOpt('periodic_interval_max', - default=60, - help='Max interval size between periodic tasks execution in ' - 'seconds.'), -] - -CONF = cfg.CONF -CONF.register_opts(periodic_opts) - - -class Service(service.Service): - - def __init__(self, topic, server, handlers, binary): - super(Service, self).__init__() - serializer = rpc.RequestContextSerializer( - objects_base.ValenceObjectSerializer()) - transport = messaging.get_transport(cfg.CONF) - # TODO(asalkeld) add support for version='x.y' - target = messaging.Target(topic=topic, server=server) - self._server = messaging.get_rpc_server(transport, target, handlers, - serializer=serializer) - self.binary = binary - - def start(self): - # servicegroup.setup(CONF, self.binary, self.tg) - self._server.start() - - def stop(self): - if self._server: - self._server.stop() - self._server.wait() - super(Service, self).stop() - - @classmethod - def create(cls, topic, server, handlers, binary): - service_obj = cls(topic, server, handlers, binary) - return service_obj - - -class API(object): - def __init__(self, transport=None, context=None, topic=None, server=None, - timeout=None): - serializer = rpc.RequestContextSerializer( - objects_base.ValenceObjectSerializer()) - if transport is None: - exmods = rpc.get_allowed_exmods() - transport = messaging.get_transport(cfg.CONF, - allowed_remote_exmods=exmods) - self._context = context - if topic is None: - topic = '' - target = messaging.Target(topic=topic, server=server) - self._client = messaging.RPCClient(transport, target, - serializer=serializer, - timeout=timeout) - - def _call(self, method, *args, **kwargs): - return self._client.call(self._context, method, *args, **kwargs) - - def _cast(self, method, *args, **kwargs): - self._client.cast(self._context, method, *args, **kwargs) - - def echo(self, message): - self._cast('echo', message=message) diff --git a/valence/config.py b/valence/config.py new file mode 100644 index 0000000..6ed5590 --- /dev/null +++ b/valence/config.py @@ -0,0 +1,68 @@ +# 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, type=str): + """Function to support default values + + Though config fallback feature could be used + Py 2.7 doesnt support it + + """ + if config.has_option(section, key): + return type(config.get(section, key)) + else: + return type(default) + + +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) + +# 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") diff --git a/valence/controller/__init__.py b/valence/controller/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/valence/controller/api.py b/valence/controller/api.py deleted file mode 100644 index 46c7f9a..0000000 --- a/valence/controller/api.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2016 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. - -"""controller API for interfacing with Other modules""" -from oslo_config import cfg -from oslo_log import log as logging -from valence.common import rpc_service - - -# The Backend API class serves as a AMQP client for communicating -# on a topic exchange specific to the controllers. This allows the ReST -# API to trigger operations on the controllers - -LOG = logging.getLogger(__name__) - - -class API(rpc_service.API): - def __init__(self, transport=None, context=None, topic=None): - if topic is None: - cfg.CONF.import_opt('topic', 'valence.controller.config', - group='controller') - super(API, self).__init__(transport, context, - topic=cfg.CONF.controller.topic) - - # Flavor Operations - - def flavor_options(self): - return self._call('flavor_options') - - def flavor_generate(self, criteria): - return self._call('flavor_generate', criteria=criteria) - - # Node(s) Operations - def list_nodes(self, filters): - return self._call('list_nodes', filters=filters) - - def get_nodebyid(self, nodeid): - return self._call('get_nodebyid', nodeid=nodeid) - - def delete_composednode(self, nodeid): - return self._call('delete_composednode', nodeid=nodeid) - - def update_node(self, nodeid): - return self._call('update_node') - - def compose_nodes(self, criteria): - return self._call('compose_nodes', criteria=criteria) - - def list_node_storages(self, data): - return self._call('list_node_storages') - - def map_node_storage(self, data): - return self._call('map_node_storage') - - def delete_node_storage(self, data): - return self._call('delete_node_storage') diff --git a/valence/controller/config.py b/valence/controller/config.py deleted file mode 100644 index 8012331..0000000 --- a/valence/controller/config.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2016 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. - -"""Config options for Valence controller Service""" - -from oslo_config import cfg -from oslo_log import log as logging -import sys - -LOG = logging.getLogger(__name__) - -CONTROLLER_OPTS = [ - cfg.StrOpt('topic', - default='valence-controller', - help='The queue to add controller tasks to.') -] - -OS_INTERFACE_OPTS = [ - cfg.StrOpt('os_admin_url', - help='Admin URL of Openstack'), - cfg.StrOpt('os_tenant', - default='admin', - help='Tenant for Openstack'), - cfg.StrOpt('os_user', - default='admin', - help='User for openstack'), - cfg.StrOpt('os_password', - default='addmin', - help='Password for openstack') -] - -controller_conf_group = cfg.OptGroup(name='controller', - title='Valence controller options') -cfg.CONF.register_group(controller_conf_group) -cfg.CONF.register_opts(CONTROLLER_OPTS, group=controller_conf_group) - -os_conf_group = cfg.OptGroup(name='undercloud', - title='Valence Openstack interface options') -cfg.CONF.register_group(os_conf_group) -cfg.CONF.register_opts(OS_INTERFACE_OPTS, group=os_conf_group) - - -def init(args, **kwargs): - # Register the configuration options - logging.register_options(cfg.CONF) - cfg.CONF(args=args, project='valence', **kwargs) - - -def setup_logging(): - """Sets up the logging options for a log with supplied name.""" - domain = "valence" - logging.setup(cfg.CONF, domain) - LOG.info("Logging enabled!") - LOG.debug("command line: %s", " ".join(sys.argv)) diff --git a/valence/controller/handlers/__init__.py b/valence/controller/handlers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/valence/controller/handlers/node_controller.py b/valence/controller/handlers/node_controller.py deleted file mode 100644 index a09acdb..0000000 --- a/valence/controller/handlers/node_controller.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (c) 2016 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_log import log as logging -from valence.common.redfish import api as rfsapi - -LOG = logging.getLogger(__name__) - - -class Handler(object): - """Valence Node RPC handler. - - These are the backend operations. They are executed by the backend ervice. - API calls via AMQP (within the ReST API) trigger the handlers to be called. - - """ - - def __init__(self): - super(Handler, self).__init__() - - def list_nodes(self, context, filters): - LOG.info(str(filters)) - return rfsapi.nodes_list(None, filters) - - def get_nodebyid(self, context, nodeid): - return rfsapi.get_nodebyid(nodeid) - - def delete_composednode(self, context, nodeid): - return rfsapi.delete_composednode(nodeid) - - def update_node(self, context, nodeid): - return {"node": "Update node attributes"} - - def compose_nodes(self, context, criteria): - """Chassis details could also be fetched and inserted""" - node_criteria = criteria["filter"] if "filter" in criteria else {} - return rfsapi.compose_node(node_criteria) - - def list_node_storages(self, context, data): - return {"node": "List the storages attached to the node"} - - def map_node_storage(self, context, data): - return {"node": "Map storages to a node"} - - def delete_node_storage(self, context, data): - return {"node": "Deleted storages mapped to a node"} diff --git a/valence/flavor/flavor.py b/valence/flavor/flavor.py index d4fc7cd..528d1cd 100644 --- a/valence/flavor/flavor.py +++ b/valence/flavor/flavor.py @@ -13,13 +13,12 @@ # under the License. from importlib import import_module -# from valence.flavor.plugins import * +import logging import os -from oslo_log import log as logging -from valence.common.redfish import api as rfs +from valence.redfish import redfish as rfs FLAVOR_PLUGIN_PATH = os.path.dirname(os.path.abspath(__file__)) + '/plugins' -logger = logging.getLogger() +LOG = logging.getLogger(__name__) def get_available_criteria(): @@ -28,27 +27,29 @@ def get_available_criteria(): if os.path.isfile(os.path.join(FLAVOR_PLUGIN_PATH, f)) and not f.startswith('__') and f.endswith('.py')] resp = [] - for p in pluginfiles: - module = import_module("valence.flavor.plugins." + p) - myclass = getattr(module, p + 'Generator') + for filename in pluginfiles: + module = import_module("valence.flavor.plugins." + filename) + myclass = getattr(module, filename + 'Generator') inst = myclass([]) - resp.append({'name': p, 'description': inst.description()}) + resp.append({'name': filename, 'description': inst.description()}) return {'criteria': resp} -def create_flavors(criteria): +def create_flavors(data): """criteria : comma seperated generator names This should be same as thier file name) """ + criteria = data["criteria"] respjson = [] - lst_nodes = rfs.nodes_list() - for g in criteria.split(","): - if g: - logger.info("Calling generator : %s ." % g) - module = __import__("valence.flavor.plugins." + g, fromlist=["*"]) - classobj = getattr(module, g + "Generator") - inst = classobj(lst_nodes) + lst_systems = rfs.systems_list() + for criteria_name in criteria.split(","): + if criteria_name: + LOG.info("Calling generator : %s ." % criteria_name) + module = __import__("valence.flavor.plugins." + criteria_name, + fromlist=["*"]) + classobj = getattr(module, criteria_name + "Generator") + inst = classobj(lst_systems) respjson.append(inst.generate()) return respjson diff --git a/valence/flavor/plugins/assettag.py b/valence/flavor/plugins/assettag.py index adfea8f..1654842 100644 --- a/valence/flavor/plugins/assettag.py +++ b/valence/flavor/plugins/assettag.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_log import log as logging +import logging import re from valence.flavor.generatorbase import generatorbase @@ -29,7 +29,7 @@ class assettagGenerator(generatorbase): def generate(self): LOG.info("Default Generator") for node in self.nodes: - LOG.info("Node ID " + node['nodeid']) + LOG.info("Node ID " + node['id']) location = node['location'] location = location.split('Sled')[0] location_lst = re.split("(\d+)", location) diff --git a/valence/flavor/plugins/default.py b/valence/flavor/plugins/default.py index e464006..932c0fc 100644 --- a/valence/flavor/plugins/default.py +++ b/valence/flavor/plugins/default.py @@ -12,10 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_log import log as logging +import logging from valence.flavor.generatorbase import generatorbase -LOG = logging.getLogger() +LOG = logging.getLogger(__name__) class defaultGenerator(generatorbase): @@ -29,14 +29,15 @@ class defaultGenerator(generatorbase): def generate(self): LOG.info("Default Generator") for node in self.nodes: - LOG.info("Node ID " + node['nodeid']) + LOG.debug("Node ID " + node['id']) location = node['location'] + LOG.debug(location) location_lst = location.split("_") location_lst = list(filter(None, location_lst)) - extraspecs = ( - {l[0]: l[1] for l in (l.split(":") for l in location_lst)}) - name = self.prepend_name + location - return { + extraspecs = ({l[0]: l[1] + for l in (l.split(":") for l in location_lst)}) + name = self.prepend_name + node['id'] + return [ self._flavor_template("L_" + name, node['ram'], node['cpu']["count"], @@ -52,4 +53,4 @@ class defaultGenerator(generatorbase): int(node['cpu']["count"]) / 4, int(node['storage']) / 4, extraspecs) - } + ] diff --git a/valence/flavor/plugins/example.py b/valence/flavor/plugins/example.py index a041476..4daee8f 100644 --- a/valence/flavor/plugins/example.py +++ b/valence/flavor/plugins/example.py @@ -12,10 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_log import log as logging +import logging from valence.flavor.generatorbase import generatorbase -logger = logging.getLogger() +LOG = logging.getLogger(__name__) class exampleGenerator(generatorbase): @@ -23,5 +23,5 @@ class exampleGenerator(generatorbase): generatorbase.__init__(self, nodes) def generate(self): - logger.info("Example Flavor Generate") - return {"Error": "Example Flavor Generator- Not Yet Implemented"} + LOG.info("Example Flavor Generate") + return {"Info": "Example Flavor Generator- Not Yet Implemented"} diff --git a/valence/objects/__init__.py b/valence/objects/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/valence/objects/base.py b/valence/objects/base.py deleted file mode 100644 index d829f73..0000000 --- a/valence/objects/base.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) 2016 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. - -"""Valence common internal object model""" - -from oslo_versionedobjects import base as ovoo_base -from oslo_versionedobjects import fields as ovoo_fields - - -remotable_classmethod = ovoo_base.remotable_classmethod -remotable = ovoo_base.remotable - - -class ValenceObjectRegistry(ovoo_base.VersionedObjectRegistry): - pass - - -class ValenceObject(ovoo_base.VersionedObject): - """Base class and object factory. - - This forms the base of all objects that can be remoted or instantiated - via RPC. Simply defining a class that inherits from this base class - will make it remotely instantiatable. Objects should implement the - necessary "get" classmethod routines as well as "save" object methods - as appropriate. - """ - OBJ_PROJECT_NAMESPACE = 'Valence' - - def as_dict(self): - return {k: getattr(self, k) - for k in self.fields - if self.obj_attr_is_set(k)} - - -class ValenceObjectDictCompat(ovoo_base.VersionedObjectDictCompat): - pass - - -class ValencePersistentObject(object): - """Mixin class for Persistent objects. - - This adds the fields that we use in common for all persistent objects. - """ - fields = { - 'created_at': ovoo_fields.DateTimeField(nullable=True), - 'updated_at': ovoo_fields.DateTimeField(nullable=True), - } - - -class ValenceObjectSerializer(ovoo_base.VersionedObjectSerializer): - # Base class to use for object hydration - OBJ_BASE_CLASS = ValenceObject diff --git a/valence/api/controllers/v1/__init__.py b/valence/redfish/__init__.py similarity index 100% rename from valence/api/controllers/v1/__init__.py rename to valence/redfish/__init__.py diff --git a/valence/common/redfish/api.py b/valence/redfish/redfish.py similarity index 74% rename from valence/common/redfish/api.py rename to valence/redfish/redfish.py index 33ba1cb..1e9c850 100644 --- a/valence/common/redfish/api.py +++ b/valence/redfish/redfish.py @@ -14,14 +14,14 @@ # under the License. import json -from oslo_config import cfg -from oslo_log import log as logging +import logging import requests from requests.auth import HTTPBasicAuth -from valence.common.redfish import tree +from valence import config as cfg +from valence.redfish import tree + LOG = logging.getLogger(__name__) -cfg.CONF.import_group('podm', 'valence.common.redfish.config') def get_rfs_url(serviceext): @@ -29,17 +29,18 @@ def get_rfs_url(serviceext): INDEX = '' # '/index.json' if REDFISH_BASE_EXT in serviceext: - return cfg.CONF.podm.url + serviceext + INDEX + return cfg.podm_url + serviceext + INDEX else: - return cfg.CONF.podm.url + REDFISH_BASE_EXT + serviceext + INDEX + return cfg.podm_url + REDFISH_BASE_EXT + serviceext + INDEX 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.CONF.podm.user - httppwd = cfg.CONF.podm.password + httpuser = cfg.podm_user + httppwd = cfg.podm_password resp = None + LOG.debug(url) try: resp = requests.request(method, url, verify=False, auth=HTTPBasicAuth(httpuser, httppwd), @@ -92,57 +93,6 @@ def generic_filter(jsonContent, filterConditions): return is_filter_passed -def get_details(source): - returnJSONObj = [] - members = source['Members'] - for member in members: - resource = member['@odata.id'] - resp = send_request(resource) - memberJson = resp.json() - memberJsonObj = json.loads(memberJson) - returnJSONObj[resource] = memberJsonObj - return returnJSONObj - - -def systemdetails(): - returnJSONObj = [] - parsed = send_request('Systems') - members = parsed['Members'] - for member in members: - resource = member['@odata.id'] - resp = send_request(resource) - memberJsonContent = resp.json() - memberJSONObj = json.loads(memberJsonContent) - returnJSONObj[resource] = memberJSONObj - return(json.dumps(returnJSONObj)) - - -def nodedetails(): - returnJSONObj = [] - parsed = send_request('Nodes') - members = parsed['Members'] - for member in members: - resource = member['@odata.id'] - resp = send_request(resource) - memberJSONObj = resp.json() - returnJSONObj[resource] = memberJSONObj - return(json.dumps(returnJSONObj)) - - -def podsdetails(): - jsonContent = send_request('Chassis') - pods = filter_chassis(jsonContent, 'Pod') - podsDetails = get_details(pods) - return json.dumps(podsDetails) - - -def racksdetails(): - jsonContent = send_request('Chassis') - racks = filter_chassis(jsonContent, 'Rack') - racksDetails = get_details(racks) - return json.dumps(racksDetails) - - def racks(): jsonContent = send_request('Chassis') racks = filter_chassis(jsonContent, 'Rack') @@ -165,36 +115,39 @@ def urls2list(url): return [] -def extract_val(data, path): +def extract_val(data, path, defaultval=None): # function to select value at particularpath patharr = path.split("/") for p in patharr: data = data[p] + data = (data if data else defaultval) return data def node_cpu_details(nodeurl): cpucnt = 0 cpuarch = "" + cpumodel = "" cpulist = urls2list(nodeurl + '/Processors') for lnk in cpulist: LOG.info("Processing CPU %s" % lnk) resp = send_request(lnk) respdata = resp.json() - cpucnt += extract_val(respdata, "TotalCores") - cpuarch = extract_val(respdata, "InstructionSet") - cpumodel = extract_val(respdata, "Model") + # Check if CPU data is populated. It also may have NULL values + cpucnt += extract_val(respdata, "TotalCores", 0) + cpuarch = extract_val(respdata, "InstructionSet", "") + cpumodel = extract_val(respdata, "Model", "") LOG.debug(" Cpu details %s: %d: %s: %s " % (nodeurl, cpucnt, cpuarch, cpumodel)) - return {"count": str(cpucnt), "arch": cpuarch, "model": cpumodel} + return {"cores": str(cpucnt), "arch": cpuarch, "model": cpumodel} def node_ram_details(nodeurl): # this extracts the RAM and returns as dictionary resp = send_request(nodeurl) respjson = resp.json() - ram = extract_val(respjson, "MemorySummary/TotalSystemMemoryGiB") - return str(ram) if ram else "0" + ram = extract_val(respjson, "MemorySummary/TotalSystemMemoryGiB", "0") + return str(ram) def node_nw_details(nodeurl): @@ -214,6 +167,8 @@ def node_storage_details(nodeurl): resp = send_request(lnk) respbody = resp.json() hdds = extract_val(respbody, "Devices") + if not hdds: + continue for sd in hdds: if "CapacityBytes" in sd: if sd["CapacityBytes"] is not None: @@ -223,21 +178,17 @@ def node_storage_details(nodeurl): return str(storagecnt / 1073741824).split(".")[0] -def systems_list(count=None, filters={}): - # comment the count value which is set to 2 now.. +def systems_list(filters={}): # list of nodes with hardware details needed for flavor creation - # count = 2 lst_systems = [] systemurllist = urls2list("Systems") podmtree = build_hierarchy_tree() - - for lnk in systemurllist[:count]: + LOG.info(systemurllist) + for lnk in systemurllist: filterPassed = True resp = send_request(lnk) system = resp.json() - # this below code need to be changed when proper query mechanism - # is implemented if any(filters): filterPassed = generic_filter(system, filters) if not filterPassed: @@ -250,10 +201,10 @@ def systems_list(count=None, filters={}): ram = node_ram_details(lnk) nw = node_nw_details(lnk) storage = node_storage_details(lnk) - node = {"nodeid": systemid, "cpu": cpu, - "ram": ram, "storage": storage, - "nw": nw, "location": systemlocation, - "uuid": systemuuid} + system = {"id": systemid, "cpu": cpu, + "ram": ram, "storage": storage, + "nw": nw, "location": systemlocation, + "uuid": systemuuid} # filter based on RAM, CPU, NETWORK..etc if 'ram' in filters: @@ -274,8 +225,7 @@ def systems_list(count=None, filters={}): else False) if filterPassed: - lst_systems.append(node) - # LOG.info(str(node)) + lst_systems.append(system) return lst_systems @@ -315,9 +265,12 @@ def get_chassis_list(): return lst_chassis +def get_systembyid(systemid): + return systems_list({"Id": systemid}) + + def get_nodebyid(nodeid): - resp = send_request("Nodes/" + nodeid) - return resp.json() + return nodes_list({"Id": nodeid}) def build_hierarchy_tree(): @@ -338,18 +291,16 @@ def build_hierarchy_tree(): return podmtree -def compose_node(criteria={}): +def compose_node(data): composeurl = "Nodes/Actions/Allocate" headers = {'Content-type': 'application/json'} + criteria = data["criteria"] if not criteria: resp = send_request(composeurl, "POST", headers=headers) else: resp = send_request(composeurl, "POST", json=criteria, headers=headers) - LOG.info(resp.headers) - LOG.info(resp.text) - LOG.info(resp.status_code) - composednode = resp.headers['Location'] + composednode = resp.headers['Location'] return {"node": composednode} @@ -359,10 +310,9 @@ def delete_composednode(nodeid): return resp -def nodes_list(count=None, filters={}): - # comment the count value which is set to 2 now.. +def nodes_list(filters={}): # list of nodes with hardware details needed for flavor creation - # count = 2 + LOG.debug(filters) lst_nodes = [] nodeurllist = urls2list("Nodes") # podmtree = build_hierarchy_tree() @@ -376,10 +326,9 @@ def nodes_list(count=None, filters={}): else: node = resp.json() - # this below code need to be changed when proper query mechanism - # is implemented if any(filters): filterPassed = generic_filter(node, filters) + LOG.info("FILTER PASSED" + str(filterPassed)) if not filterPassed: continue @@ -392,25 +341,41 @@ def nodes_list(count=None, filters={}): cpu = {} ram = 0 nw = 0 - localstorage = node_storage_details(nodesystemurl) - if "Processors" in node: - cpu = {"count": node["Processors"]["Count"], - "model": node["Processors"]["Model"]} + storage = node_storage_details(nodesystemurl) + cpu = node_cpu_details(lnk) if "Memory" in node: ram = node["Memory"]["TotalSystemMemoryGiB"] - if "EthernetInterfaces" in node["Links"] and node[ - "Links"]["EthernetInterfaces"]: + if ("EthernetInterfaces" in node["Links"] and + node["Links"]["EthernetInterfaces"]): nw = len(node["Links"]["EthernetInterfaces"]) bmcip = "127.0.0.1" # system['Oem']['Dell_G5MC']['BmcIp'] bmcmac = "00:00:00:00:00" # system['Oem']['Dell_G5MC']['BmcMac'] - node = {"nodeid": nodeid, "cpu": cpu, - "ram": ram, "storage": localstorage, + node = {"id": nodeid, "cpu": cpu, + "ram": ram, "storage": storage, "nw": nw, "location": nodelocation, "uuid": nodeuuid, "bmcip": bmcip, "bmcmac": bmcmac} + + # filter based on RAM, CPU, NETWORK..etc + if 'ram' in filters: + filterPassed = (True + if int(ram) >= int(filters['ram']) + else False) + + # filter based on RAM, CPU, NETWORK..etc + if 'nw' in filters: + filterPassed = (True + if int(nw) >= int(filters['nw']) + else False) + + # filter based on RAM, CPU, NETWORK..etc + if 'storage' in filters: + filterPassed = (True + if int(storage) >= int(filters['storage']) + else False) + if filterPassed: lst_nodes.append(node) - # LOG.info(str(node)) - return lst_nodes + return lst_nodes diff --git a/valence/common/redfish/tree.py b/valence/redfish/tree.py similarity index 100% rename from valence/common/redfish/tree.py rename to valence/redfish/tree.py diff --git a/valence/run.py b/valence/run.py new file mode 100755 index 0000000..89ed654 --- /dev/null +++ b/valence/run.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +# copyright (c) 2016 Intel, Inc. +# +# 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 logging +from valence.api.route import app as application +from valence import config as cfg + +LOG = logging.getLogger(__name__) + + +def main(): + application.run(host=cfg.bind_host, port=cfg.bind_port, debug=cfg.debug) + LOG.info(("Valence Server on http://%(host)s:%(port)s"), + {'host': cfg.bind_host, 'port': cfg.bind_port}) + +if __name__ == '__main__': + main() diff --git a/valence/tests/__init__.py b/valence/tests/__init__.py index 2f6e1f9..97bdf42 100644 --- a/valence/tests/__init__.py +++ b/valence/tests/__init__.py @@ -1,7 +1,5 @@ -import os -from pecan import set_config -from pecan.testing import load_test_app from unittest import TestCase +from valence.api.route import app __all__ = ['FunctionalTest'] @@ -15,10 +13,8 @@ class FunctionalTest(TestCase): """ def setUp(self): - self.app = load_test_app(os.path.join( - os.path.dirname(__file__), - 'config.py' - )) + self.app = app.test_client() + self.app.testing = True def tearDown(self): - set_config({}, overwrite=True) + pass diff --git a/valence/tests/functional/test_functional.py b/valence/tests/functional/test_functional.py index 33b8321..2a5c2ed 100644 --- a/valence/tests/functional/test_functional.py +++ b/valence/tests/functional/test_functional.py @@ -1,22 +1,12 @@ from valence.tests import FunctionalTest -# from unittest import TestCase -# from webtest import TestApp class TestRootController(FunctionalTest): - def test_get(self): + def test_root_get(self): response = self.app.get('/') - assert response.status_int == 200 + assert response.status_code == 200 - def test_search(self): - response = self.app.post('/', params={'q': 'RestController'}) - assert response.status_int == 302 - assert response.headers['Location'] == ( - 'http://pecan.readthedocs.org/en/latest/search.html' - '?q=RestController' - ) - - def test_get_not_found(self): - response = self.app.get('/a/bogus/url', expect_errors=True) - assert response.status_int == 404 + def test_v1_get(self): + response = self.app.get('/v1') + assert response.status_code == 200