From eeff3ddcb69f68bc1ab79356cb30fdbd09b8e8d4 Mon Sep 17 00:00:00 2001 From: Mikyung Kang Date: Tue, 8 Jan 2013 22:56:14 +0900 Subject: [PATCH] CLI for bare-metal database sync. Part 3 of 6: blueprint general-bare-metal-provisioning-framework. Change-Id: Ia19ce00edb84aa924c2ab2c9c2217f6b49073d69 Co-authored-by: Mikyung Kang Co-authored-by: David Kang Co-authored-by: Ken Igarashi Co-authored-by: Arata Notsu --- bin/nova-baremetal-manage | 234 ++++++++++++++++++ doc/source/man/nova-baremetal-manage.rst | 67 +++++ .../baremetal/test_nova_baremetal_manage.py | 49 ++++ setup.py | 1 + 4 files changed, 351 insertions(+) create mode 100755 bin/nova-baremetal-manage create mode 100644 doc/source/man/nova-baremetal-manage.rst create mode 100644 nova/tests/baremetal/test_nova_baremetal_manage.py diff --git a/bin/nova-baremetal-manage b/bin/nova-baremetal-manage new file mode 100755 index 000000000000..34a98caf25f1 --- /dev/null +++ b/bin/nova-baremetal-manage @@ -0,0 +1,234 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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. + +# Interactive shell based on Django: +# +# Copyright (c) 2005, the Lawrence Journal-World +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of Django nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +""" + CLI interface for nova bare-metal management. +""" + +import ast +import errno +import gettext +import math +import netaddr +import optparse +import os +import sys + + +# If ../nova/__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, 'nova', '__init__.py')): + sys.path.insert(0, POSSIBLE_TOPDIR) + +gettext.install('nova', unicode=1) + +from nova import config +from nova import context +from nova import exception +from nova.openstack.common import cfg +from nova.openstack.common import cliutils +from nova.openstack.common import importutils +from nova.openstack.common import log as logging +from nova.openstack.common import rpc +from nova.openstack.common import timeutils +from nova import utils +from nova import version +from nova.virt.baremetal import db as bmdb +from nova.virt.baremetal.db import migration as bmdb_migration + +CONF = cfg.CONF + + +# Decorators for actions +def args(*args, **kwargs): + def _decorator(func): + func.__dict__.setdefault('args', []).insert(0, (args, kwargs)) + return func + return _decorator + + +class BareMetalDbCommands(object): + """Class for managing the bare-metal database.""" + + def __init__(self): + pass + + @args('--version', dest='version', metavar='', + help='Bare-metal Database version') + def sync(self, version=None): + """Sync the database up to the most recent version.""" + bmdb_migration.db_sync(version) + + def version(self): + """Print the current database version.""" + v = bmdb_migration.db_version() + print(v) + # return for unittest + return v + + +CATEGORIES = { + 'db': BareMetalDbCommands, +} + + +def methods_of(obj): + """Get all callable methods of an object that don't start with underscore + returns a list of tuples of the form (method_name, method)""" + result = [] + for i in dir(obj): + if callable(getattr(obj, i)) and not i.startswith('_'): + result.append((i, getattr(obj, i))) + return result + + +def add_command_parsers(subparsers): + parser = subparsers.add_parser('bash-completion') + parser.add_argument('query_category', nargs='?') + + for category in CATEGORIES: + command_object = CATEGORIES[category]() + + parser = subparsers.add_parser(category) + parser.set_defaults(command_object=command_object) + + category_subparsers = parser.add_subparsers(dest='action') + + for (action, action_fn) in methods_of(command_object): + parser = category_subparsers.add_parser(action) + + action_kwargs = [] + for args, kwargs in getattr(action_fn, 'args', []): + action_kwargs.append(kwargs['dest']) + kwargs['dest'] = 'action_kwarg_' + kwargs['dest'] + parser.add_argument(*args, **kwargs) + + parser.set_defaults(action_fn=action_fn) + parser.set_defaults(action_kwargs=action_kwargs) + + parser.add_argument('action_args', nargs='*') + + +category_opt = cfg.SubCommandOpt('category', + title='Command categories', + help='Available categories', + handler=add_command_parsers) + + +def main(): + """Parse options and call the appropriate class/method.""" + CONF.register_cli_opt(category_opt) + try: + config.parse_args(sys.argv) + logging.setup("nova") + except cfg.ConfigFilesNotFoundError: + cfgfile = CONF.config_file[-1] if CONF.config_file else None + if cfgfile and not os.access(cfgfile, os.R_OK): + st = os.stat(cfgfile) + print(_("Could not read %s. Re-running with sudo") % cfgfile) + try: + os.execvp('sudo', ['sudo', '-u', '#%s' % st.st_uid] + sys.argv) + except Exception: + print(_('sudo failed, continuing as if nothing happened')) + + print(_('Please re-run nova-manage as root.')) + sys.exit(2) + + if CONF.category.name == "version": + print(_("%(version)s (%(vcs)s)") % + {'version': version.version_string(), + 'vcs': version.version_string_with_vcs()}) + sys.exit(0) + + if CONF.category.name == "bash-completion": + if not CONF.category.query_category: + print(" ".join(CATEGORIES.keys())) + elif CONF.category.query_category in CATEGORIES: + fn = CATEGORIES[CONF.category.query_category] + command_object = fn() + actions = methods_of(command_object) + print(" ".join([k for (k, v) in actions])) + sys.exit(0) + + fn = CONF.category.action_fn + fn_args = [arg.decode('utf-8') for arg in CONF.category.action_args] + fn_kwargs = {} + for k in CONF.category.action_kwargs: + v = getattr(CONF.category, 'action_kwarg_' + k) + if v is None: + continue + if isinstance(v, basestring): + v = v.decode('utf-8') + fn_kwargs[k] = v + + # call the action with the remaining arguments + # check arguments + try: + cliutils.validate_args(fn, *fn_args, **fn_kwargs) + except cliutils.MissingArgs as e: + print(fn.__doc__) + parser.print_help() + print(e) + sys.exit(1) + try: + fn(*fn_args, **fn_kwargs) + sys.exit(0) + except Exception: + print(_("Command failed, please check log for more info")) + raise + + +if __name__ == '__main__': + main() diff --git a/doc/source/man/nova-baremetal-manage.rst b/doc/source/man/nova-baremetal-manage.rst new file mode 100644 index 000000000000..1fab368e5dfd --- /dev/null +++ b/doc/source/man/nova-baremetal-manage.rst @@ -0,0 +1,67 @@ +===================== +nova-baremetal-manage +===================== + +------------------------------------------------------ +Manage bare-metal DB in OpenStack Nova +------------------------------------------------------ + +:Author: openstack@lists.launchpad.net +:Date: 2012-10-17 +:Copyright: OpenStack LLC +:Version: 2013.1 +:Manual section: 1 +:Manual group: cloud computing + +SYNOPSIS +======== + + nova-baremetal-manage [] + +DESCRIPTION +=========== + +nova-baremetal-manage manages bare-metal DB schema. + +OPTIONS +======= + +The standard pattern for executing a nova-baremetal-manage command is: +``nova-baremetal-manage []`` + +Run without arguments to see a list of available command categories: +``nova-baremetal-manage`` + +Categories are db. Detailed descriptions are below. + +You can also run with a category argument such as "db" to see a list of all commands in that category: +``nova-baremetal-manage db`` + +These sections describe the available categories and arguments for nova-baremetal-manage. + +Bare-Metal DB +~~~~~~~~~~~~~ + +``nova-baremetal-manage db version`` + + Print the current database version. + +``nova-baremetal-manage db sync`` + + Sync the database up to the most recent version. This is the standard way to create the db as well. + +FILES +======== + +/etc/nova/nova.conf: get location of bare-metal DB + +SEE ALSO +======== + +* `OpenStack Nova `__ + +BUGS +==== + +* Nova is maintained in Launchpad so you can view current bugs at `OpenStack Nova `__ + diff --git a/nova/tests/baremetal/test_nova_baremetal_manage.py b/nova/tests/baremetal/test_nova_baremetal_manage.py new file mode 100644 index 000000000000..4d152a028690 --- /dev/null +++ b/nova/tests/baremetal/test_nova_baremetal_manage.py @@ -0,0 +1,49 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 NTT DOCOMO, INC. +# Copyright 2011 OpenStack LLC +# Copyright 2011 Ilya Alekseyev +# +# 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 imp +import os +import sys + +from nova import context +from nova import test +from nova.virt.baremetal import db as bmdb + +from nova.tests.baremetal.db import base as bm_db_base + +TOPDIR = os.path.normpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + os.pardir, + os.pardir, + os.pardir)) +BM_MAN_PATH = os.path.join(TOPDIR, 'bin', 'nova-baremetal-manage') + +sys.dont_write_bytecode = True +bm_man = imp.load_source('bm_man', BM_MAN_PATH) +sys.dont_write_bytecode = False + + +class BareMetalDbCommandsTestCase(bm_db_base.BMDBTestCase): + def setUp(self): + super(BareMetalDbCommandsTestCase, self).setUp() + self.commands = bm_man.BareMetalDbCommands() + + def test_sync_and_version(self): + self.commands.sync() + v = self.commands.version() + self.assertTrue(v > 0) diff --git a/setup.py b/setup.py index b04ac2b4ab0b..12de5c4d6816 100644 --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ setuptools.setup(name='nova', 'bin/nova-api-metadata', 'bin/nova-api-os-compute', 'bin/nova-baremetal-deploy-helper', + 'bin/nova-baremetal-manage', 'bin/nova-rpc-zmq-receiver', 'bin/nova-cells', 'bin/nova-cert',