diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst new file mode 100644 index 000000000..0bf87d033 --- /dev/null +++ b/doc/source/cli/index.rst @@ -0,0 +1,25 @@ +.. + 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. + +Command-line Utilities +====================== + +In this section you will find information on placement's command line +utilities: + + +.. toctree:: + :maxdepth: 1 + + placement-manage + diff --git a/doc/source/cli/placement-manage.rst b/doc/source/cli/placement-manage.rst new file mode 100644 index 000000000..b3c104f65 --- /dev/null +++ b/doc/source/cli/placement-manage.rst @@ -0,0 +1,67 @@ +.. + 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. + +================ +placement-manage +================ + + +Synopsis +======== + +:: + + placement-manage + +Description +=========== + +:program:`placement-manage` is used to perform administrative tasks with the +placement service. It is designed for use by operators and deployers. + +Options +======= + +The standard pattern for executing a ``placement-manage`` command is:: + + placement-manage [-h] [--config-dir DIR] [--config-file PATH] + [] + +Run without arguments to see a list of available command categories:: + + placement-manage + +You can also run with a category argument such as ``db`` to see a list of all +commands in that category:: + + placement-manage db + +Configuration options (for example the ``[placement_database]/connection`` +URL) are by default found in a file at ``/etc/placement/placement.conf``. The +``config-dir`` and ``config-file`` arguments may be used to select a different +file. + +The following sections describe the available categories and arguments for +placement-manage. + +Placement Database +~~~~~~~~~~~~~~~~~~ + +``placement-manage db version`` + Print the current database version. + +``placement-manage db sync`` + Upgrade the database schema to the most recent version. The local database + connection is determined by ``[placement_database]/connection`` in the + configuration file used by placement-manage. If the ``connection`` option + is not set, the command will fail. The defined database must already exist. diff --git a/doc/source/index.rst b/doc/source/index.rst index b84ab3085..e953f3114 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -42,12 +42,15 @@ example, available disk may be solid state drives (SSD). References ~~~~~~~~~~ +For an overview of some of the features provided by placement, see +:doc:`Placement Usage `. + +For a command line reference, see :doc:`cli/index`. + The following specifications represent the stages of design and development of resource providers and the Placement service. Implementation details may have changed or be partially complete at this time. -For an overview of some of the features, see the -:doc:`Placement Usage `. * `Generic Resource Pools `_ * `Compute Node Inventory `_ @@ -356,6 +359,7 @@ contribute to the placement project. .. toctree:: :hidden: + cli/index configuration/index contributor/index contributor/api-ref-guideline diff --git a/placement/cmd/__init__.py b/placement/cmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/placement/cmd/manage.py b/placement/cmd/manage.py new file mode 100644 index 000000000..dea2b8086 --- /dev/null +++ b/placement/cmd/manage.py @@ -0,0 +1,84 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from oslo_config import cfg +import pbr.version + +from placement import conf +from placement.db.sqlalchemy import migration +from placement import db_api +from placement.i18n import _ + +version_info = pbr.version.VersionInfo('openstack-placement') + + +class DbCommands(object): + + def db_sync(self): + # Let exceptions raise for now, they will go to stderr. + migration.upgrade('head') + return 0 + + def db_version(self): + print(migration.version()) + return 0 + + +def add_db_command_parsers(subparsers): + command_object = DbCommands() + + # If we set False here, we avoid having an exit during the parse + # args part of CONF processing and we can thus print out meaningful + # help text. + subparsers.required = False + parser = subparsers.add_parser('db') + parser.set_defaults(func=parser.print_help) + db_parser = parser.add_subparsers(description='database commands') + + help = _('Sync the datatabse to the current version.') + sync_parser = db_parser.add_parser('sync', help=help, description=help) + sync_parser.set_defaults(func=command_object.db_sync) + + help = _('Report the current database version.') + version_parser = db_parser.add_parser( + 'version', help=help, description=help) + version_parser.set_defaults(func=command_object.db_version) + + +def setup_commands(): + # This is a separate method because it facilitates unit testing. + # Use an additional SubCommandOpt and parser for each new sub command. + command_opt = cfg.SubCommandOpt( + 'db', dest='command', title='Command', help=_('Available DB commands'), + handler=add_db_command_parsers) + return [command_opt] + + +def main(): + CONF = conf.CONF + command_opts = setup_commands() + CONF.register_cli_opts(command_opts) + CONF(sys.argv[1:], project='placement', + version=version_info.version_string(), + default_config_files=None) + db_api.configure(CONF) + + try: + func = CONF.command.func + return_code = func() + # If return_code ends up None we assume 0. + sys.exit(return_code or 0) + except cfg.NoSuchOptError: + CONF.print_help() + sys.exit(1) diff --git a/placement/tests/unit/cmd/__init__.py b/placement/tests/unit/cmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/placement/tests/unit/cmd/test_manage.py b/placement/tests/unit/cmd/test_manage.py new file mode 100644 index 000000000..746b0b58e --- /dev/null +++ b/placement/tests/unit/cmd/test_manage.py @@ -0,0 +1,81 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import mock +from oslo_config import cfg +from oslo_config import fixture as config_fixture +from oslotest import output +import six +import testtools + +from placement.cmd import manage + + +class TestCommandParsers(testtools.TestCase): + + def setUp(self): + super(TestCommandParsers, self).setUp() + self.conf = cfg.CONF + conf_fixture = config_fixture.Config(self.conf) + self.useFixture(conf_fixture) + # Quiet output from argparse (used within oslo_config). + # If you are debugging, commenting this out might be useful. + self.useFixture(output.CaptureOutput(do_stderr=True)) + # We don't use a database, but we need to set the opt as + # it's required for a valid config. + conf_fixture.config(group="placement_database", connection='sqlite://') + command_opts = manage.setup_commands() + # Command line opts must be registered on the conf_fixture, otherwise + # they carry over globally. + conf_fixture.register_cli_opts(command_opts) + + def test_commands_associated(self): + """Test that commands get parsed as desired. + + This leaves out --version, which is built into oslo.config's handling. + """ + for command, args in [ + ('db_version', ['db', 'version']), + ('db_sync', ['db', 'sync']), + ]: + with mock.patch('placement.cmd.manage.DbCommands.' + + command) as mock_command: + self.conf(args, default_config_files=[]) + self.conf.command.func() + mock_command.assert_called_once_with() + + def test_non_command(self): + """A non-existent command should fail.""" + self.assertRaises(SystemExit, + self.conf, ['pony'], default_config_files=[]) + + def test_empty_command(self): + """An empty command should create no func.""" + # Python 2.7 and 3.x behave differently here, but the result is + # satisfactory. Both result in some help output, but the Python 3 + # help is better. + def parse_conf(): + self.conf([], default_config_files=[]) + + def get_func(): + return self.conf.command.func + + if six.PY2: + self.assertRaises(SystemExit, parse_conf) + else: + parse_conf() + self.assertRaises(cfg.NoSuchOptError, get_func) + + def test_too_many_args(self): + self.assertRaises(SystemExit, + self.conf, ['version', '5'], default_config_files=[]) diff --git a/setup.cfg b/setup.cfg index ced2d3f2a..22c30af96 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,10 +41,8 @@ oslo.policy.policies = placement = placement.policies:list_rules console_scripts = - # TODO(cdent): We will need some kind of placement-manage and - # placement-status, eventually - # nova-manage = nova.cmd.manage:main - # nova-status = nova.cmd.status:main + # TODO(cdent): We will need some kind of placement-status, eventually + placement-manage = placement.cmd.manage:main wsgi_scripts = placement-api = placement.wsgi:init_application