diff --git a/bin/savanna-manage b/bin/savanna-manage new file mode 100644 index 0000000000..34908e1314 --- /dev/null +++ b/bin/savanna-manage @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# Copyright (c) 2013 Mirantis 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 os +import sys + +# If ../savanna/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, + 'savanna', + '__init__.py')): + sys.path.insert(0, possible_topdir) + + +from savanna import cli + + +if __name__ == '__main__': + dev_conf = os.path.join(possible_topdir, + 'etc', + 'savanna', + 'savanna.conf') + config_files = None + if os.path.exists(dev_conf): + config_files = [dev_conf] + + cli.main(argv=sys.argv, config_files=config_files) diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index 713c5d937f..0a260f5bd2 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -136,25 +136,25 @@ You should see the output similar to the following: # URL for sqlalchemy database (string value) #database_uri=sqlite:////tmp/savanna-server.db +**Note:** Config file could be specified for ``savanna-api`` and ``savanna-manage`` commands using ``--config-file`` flag. -3.7 To run Savanna from created environment just call: +3.7 To initialize database and generate templates call: .. sourcecode:: bash - .venv/bin/python bin/savanna-api --reset-db --stub-data --allow-cluster-ops + .venv/bin/python bin/savanna-manage reset-db --with-gen-templates -**Note:** ``--reset-db`` and ``--stub-data`` parameters should be inserted only with the first Savanna-API startup. -With these parameters supplied Savanna will create sqlite db with predefined data in ``/tmp/savanna-server.db`` +This command creates database file from scratch and generates some node templates. -Next times these parameters should be omited: +3.8 To run Savanna from created environment just call: .. sourcecode:: bash - .venv/bin/python/ bin/savanna-api --allow-cluster-ops + .venv/bin/python bin/savanna-api --allow-cluster-ops Now Savanna service is running. Further steps show how you can verify from console that Savanna API works properly. -3.8 First install httpie program. It allows you to send http requests to Savanna API service. +3.9 First install httpie program. It allows you to send http requests to Savanna API service. .. sourcecode:: bash @@ -162,7 +162,7 @@ Now Savanna service is running. Further steps show how you can verify from conso **Note:** sure you can use another HTTP client like curl to send requests to Savanna service -3.9 Then you need to get authentification token from OpenStack Keystone service: +3.10 Then you need to get authentification token from OpenStack Keystone service: .. sourcecode:: bash @@ -190,7 +190,7 @@ If authentication succeed, output will be as follows: **Note:** Save the token because you have to supply it with every request to Savanna in X-Auth-Token header. You will also use tenant id in request URL -3.10 Send http request to the Savanna service: +3.11 Send http request to the Savanna service: .. sourcecode:: bash diff --git a/savanna/cli.py b/savanna/cli.py new file mode 100644 index 0000000000..0fcedc64a4 --- /dev/null +++ b/savanna/cli.py @@ -0,0 +1,88 @@ +# Copyright (c) 2013 Mirantis 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 Flask + +from oslo.config import cfg +from savanna.openstack.common import log +from savanna.storage.defaults import setup_defaults +from savanna.storage.storage import setup_storage, DB + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +class BaseCmd(object): + name = None + + @classmethod + def add_argument_parser(cls, subparsers): + parser = subparsers.add_parser(cls.name, help=cls.__doc__) + parser.set_defaults(cmd_class=cls) + return parser + + +class ResetDbCmd(BaseCmd): + """Reset the database.""" + + name = 'reset-db' + + @classmethod + def add_argument_parser(cls, subparsers): + parser = super(ResetDbCmd, cls).add_argument_parser(subparsers) + parser.add_argument('--with-gen-templates', action='store_true') + return parser + + @staticmethod + def main(): + gen = CONF.command.with_gen_templates + + app = Flask('savanna.manage') + setup_storage(app) + + DB.drop_all() + DB.create_all() + + setup_defaults(True, gen) + + LOG.info("DB has been removed and created from scratch, " + "gen templates: %s", gen) + + +CLI_COMMANDS = [ + ResetDbCmd, +] + + +def add_command_parsers(subparsers): + for cmd in CLI_COMMANDS: + cmd.add_argument_parser(subparsers) + + +command_opt = cfg.SubCommandOpt('command', + title='Commands', + help='Available commands', + handler=add_command_parsers) + + +def main(argv=None, config_files=None): + CONF.register_cli_opt(command_opt) + CONF(args=argv[1:], + project='savanna', + usage='%(prog)s [' + '|'.join( + [cmd.name for cmd in CLI_COMMANDS]) + ']', + default_config_files=config_files) + log.setup("savanna") + CONF.command.cmd_class.main() diff --git a/savanna/config.py b/savanna/config.py index 04f44b0e2a..14c76bdb3e 100644 --- a/savanna/config.py +++ b/savanna/config.py @@ -20,10 +20,6 @@ cli_opts = [ help='set host'), cfg.IntOpt('port', default=8080, help='set port'), - cfg.BoolOpt('reset-db', default=False, - help='resets DB'), - cfg.BoolOpt('stub-data', default=False, - help='populates DB with stub data'), cfg.BoolOpt('allow-cluster-ops', default=False, help='without that option' ' the application operates in dry run mode and does not ' diff --git a/savanna/main.py b/savanna/main.py index a1c030fa72..1437cbca0e 100644 --- a/savanna/main.py +++ b/savanna/main.py @@ -23,7 +23,6 @@ from savanna.api import v02 as api_v02 from savanna.middleware.auth_valid import filter_factory as auth_valid from savanna.utils.scheduler import setup_scheduler -from savanna.storage.defaults import setup_defaults from savanna.utils.api import render from savanna.storage.storage import setup_storage @@ -60,18 +59,8 @@ opts = [ help='Name of network which IPs are given to the VMs') ] -sqlalchemy_opts = [ - cfg.StrOpt('database_uri', - default='sqlite:////tmp/savanna-server.db', - help='URL for sqlalchemy database'), - cfg.BoolOpt('echo', - default=False, - help='Sqlalchemy echo') -] - CONF = cfg.CONF CONF.register_opts(opts) -CONF.register_opts(sqlalchemy_opts, group='sqlalchemy') def make_app(): @@ -80,9 +69,6 @@ def make_app(): """ app = Flask('savanna.api') - app.config['SQLALCHEMY_DATABASE_URI'] = CONF.sqlalchemy.database_uri - app.config['SQLALCHEMY_ECHO'] = CONF.sqlalchemy.echo - @app.route('/', methods=['GET']) def version_list(): return render({ @@ -94,7 +80,6 @@ def make_app(): app.register_blueprint(api_v02.rest, url_prefix='/v0.2') setup_storage(app) - setup_defaults() setup_scheduler(app) def make_json_error(ex): diff --git a/savanna/storage/defaults.py b/savanna/storage/defaults.py index 412c13b572..f92836cf3e 100644 --- a/savanna/storage/defaults.py +++ b/savanna/storage/defaults.py @@ -13,8 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from oslo.config import cfg - from savanna.storage.models import NodeProcess, NodeProcessProperty, \ NodeType, NodeTemplate, NodeTemplateConfig, Cluster, ClusterNodeCount from savanna.storage.storage import DB @@ -22,10 +20,6 @@ from savanna.openstack.common import log as logging LOG = logging.getLogger(__name__) -CONF = cfg.CONF -CONF.import_opt('reset_db', 'savanna.config') -CONF.import_opt('stub_data', 'savanna.config') - def create_node_process(name, properties): """ @@ -109,13 +103,13 @@ def create_cluster(name, base_image_id, tenant_id, templates): return cluster -def setup_defaults(): +def setup_defaults(reset_db=False, gen_templates=False): nt_jt_nn = None nt_jt = None nt_nn = None nt_tt_dn = None - if CONF.reset_db: + if reset_db: # setup default processes p_jt = create_node_process('job_tracker', [('heap_size', True, None)]) p_nn = create_node_process('name_node', [('heap_size', True, None)]) @@ -135,11 +129,13 @@ def setup_defaults(): LOG.info('New NodeType: \'%s\' %s', nt.name, [p.name.__str__() for p in nt.processes]) - if CONF.stub_data: - _setup_stub_data(nt_jt_nn, nt_jt, nt_nn, nt_tt_dn) + if gen_templates: + _generate_templates(nt_jt_nn, nt_jt, nt_nn, nt_tt_dn) + + LOG.info('All defaults has been inserted') -def _setup_stub_data(nt_jt_nn, nt_jt, nt_nn, nt_tt_dn): +def _generate_templates(nt_jt_nn, nt_jt, nt_nn, nt_tt_dn): jt_nn_small = create_node_template('jt_nn.small', nt_jt_nn.id, 'tenant-01', 'm1.small', { @@ -212,7 +208,4 @@ def _setup_stub_data(nt_jt_nn, nt_jt, nt_nn, nt_tt_dn): for tmpl in [jt_nn_small, jt_nn_medium, jt_small, jt_medium, nn_small, nn_medium, tt_dn_small, tt_dn_medium]: - LOG.info('New NodeTemplate: %s \'%s\' %s', - tmpl.id, tmpl.name, tmpl.flavor_id) - - LOG.info('All defaults has been inserted') + LOG.info('New NodeTemplate: \'%s\' %s', tmpl.name, tmpl.flavor_id) diff --git a/savanna/storage/storage.py b/savanna/storage/storage.py index 44c8fbe36f..21144bc90d 100644 --- a/savanna/storage/storage.py +++ b/savanna/storage/storage.py @@ -18,15 +18,23 @@ from oslo.config import cfg DB = SQLAlchemy() +opts = [ + cfg.StrOpt('database_uri', + default='sqlite:////tmp/savanna.db', + help='URL for sqlalchemy database'), + cfg.BoolOpt('echo', + default=False, + help='Sqlalchemy echo') +] + CONF = cfg.CONF -CONF.import_opt('reset_db', 'savanna.config') +CONF.register_opts(opts, group='sqlalchemy') def setup_storage(app): + app.config['SQLALCHEMY_DATABASE_URI'] = CONF.sqlalchemy.database_uri + app.config['SQLALCHEMY_ECHO'] = CONF.sqlalchemy.echo + DB.app = app DB.init_app(app) - - if CONF.reset_db: - DB.drop_all() - DB.create_all() diff --git a/savanna/tests/unit/test_api_v02.py b/savanna/tests/unit/test_api_v02.py index e3c53f80dc..fbbb789067 100644 --- a/savanna/tests/unit/test_api_v02.py +++ b/savanna/tests/unit/test_api_v02.py @@ -24,6 +24,7 @@ from oslo.config import cfg from savanna.main import make_app from savanna.service import api +from savanna.storage.defaults import setup_defaults from savanna.storage.models import Node, NodeTemplate from savanna.storage.storage import DB import savanna.main @@ -82,12 +83,10 @@ def _stub_auth_valid(*args, **kwargs): CONF = cfg.CONF -CONF.import_opt('reset_db', 'savanna.config') -CONF.import_opt('stub_data', 'savanna.config') CONF.import_opt('debug', 'savanna.openstack.common.log') CONF.import_opt('allow_cluster_ops', 'savanna.config') -CONF.import_opt('database_uri', 'savanna.main', group='sqlalchemy') -CONF.import_opt('echo', 'savanna.main', group='sqlalchemy') +CONF.import_opt('database_uri', 'savanna.storage.storage', group='sqlalchemy') +CONF.import_opt('echo', 'savanna.storage.storage', group='sqlalchemy') class TestApi(unittest.TestCase): @@ -96,8 +95,6 @@ class TestApi(unittest.TestCase): self.maxDiff = 10000 # override configs - CONF.set_override('reset_db', True) - CONF.set_override('stub_data', True) CONF.set_override('debug', True) CONF.set_override('allow_cluster_ops', True) # stub process CONF.set_override('database_uri', 'sqlite:///' + self.db_path, @@ -118,6 +115,10 @@ class TestApi(unittest.TestCase): app = make_app() + DB.drop_all() + DB.create_all() + setup_defaults(True, True) + LOG.debug('Test db path: %s', self.db_path) LOG.debug('Test app.config: %s', app.config) diff --git a/tools/run_pep8 b/tools/run_pep8 index 23f754da9e..c6b7f151c7 100755 --- a/tools/run_pep8 +++ b/tools/run_pep8 @@ -3,4 +3,4 @@ echo "Running PEP8 checks" echo "===================" -.venv/bin/pep8 --show-source bin bin/savanna-api savanna +.venv/bin/pep8 --show-source bin bin/savanna-api bin/savanna-manage savanna diff --git a/tools/run_pyflakes b/tools/run_pyflakes index f05dbc7f7c..2aca652c61 100755 --- a/tools/run_pyflakes +++ b/tools/run_pyflakes @@ -3,4 +3,4 @@ echo "Running PyFlakes checks" echo "=======================" -.venv/bin/pyflakes bin bin/savanna-api savanna +.venv/bin/pyflakes bin bin/savanna-api bin/savanna-manage savanna diff --git a/tools/run_pylint b/tools/run_pylint index 975cafa04d..ba992db532 100755 --- a/tools/run_pylint +++ b/tools/run_pylint @@ -3,4 +3,4 @@ echo "Running PyLint checks" echo "=====================" -.venv/bin/pylint --output-format=parseable --rcfile=.pylintrc bin/savanna-api savanna | tee pylint-report.txt +.venv/bin/pylint --output-format=parseable --rcfile=.pylintrc bin/savanna-api bin/savanna-manage savanna | tee pylint-report.txt diff --git a/tox.ini b/tox.ini index 23c1a8d6f6..aa80aff529 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ setenv = NOSE_WITH_COVERAGE=1 [testenv:pyflakes] deps = {[testenv]deps} -commands = pyflakes bin bin/savanna-api savanna setup.py +commands = pyflakes bin bin/savanna-api bin/savanna-manage savanna setup.py [testenv:venv] commands = {posargs}