Merge "Migrate CLI to cliff"

This commit is contained in:
Zuul 2018-03-15 14:28:03 +00:00 committed by Gerrit Code Review
commit 3a25646abe
4 changed files with 183 additions and 148 deletions

View File

@ -6,4 +6,4 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
six>=1.10.0 # MIT
libvirt-python!=4.1.0,>=3.5.0 # LGPLv2+
pyghmi>=1.0.22 # Apache-2.0
PrettyTable<0.8,>=0.7.1 # BSD
cliff>=2.8.0,!=2.9.0 # Apache-2.0

View File

@ -26,6 +26,14 @@ packages =
console_scripts =
vbmc = virtualbmc.cmd.vbmc:main
virtualbmc =
add = virtualbmc.cmd.vbmc:AddCommand
delete = virtualbmc.cmd.vbmc:DeleteCommand
start = virtualbmc.cmd.vbmc:StartCommand
stop = virtualbmc.cmd.vbmc:StopCommand
list = virtualbmc.cmd.vbmc:ListCommand
show = virtualbmc.cmd.vbmc:ShowCommand
[build_sphinx]
source-dir = doc/source
build-dir = doc/build

View File

@ -10,146 +10,194 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import argparse
import logging
import sys
from prettytable import PrettyTable
from cliff.app import App
from cliff.command import Command
from cliff.commandmanager import CommandManager
from cliff.lister import Lister
import virtualbmc
from virtualbmc import exception
from virtualbmc.manager import VirtualBMCManager
def main():
parser = argparse.ArgumentParser(
prog='Virtual BMC',
description='A virtual BMC for controlling virtual instances',
)
parser.add_argument('--version', action='version',
version=virtualbmc.__version__)
subparsers = parser.add_subparsers()
class AddCommand(Command):
"""Create a new BMC for a virtual machine instance"""
# http://bugs.python.org/issue9253#msg186387
subparsers.required = True
subparsers.dest = 'command'
def get_parser(self, prog_name):
parser = super(AddCommand, self).get_parser(prog_name)
# create the parser for the "add" command
parser_add = subparsers.add_parser('add', help='Add a new virtual BMC')
parser_add.add_argument('domain_name',
parser.add_argument('domain_name',
help='The name of the virtual machine')
parser_add.add_argument('--username',
parser.add_argument('--username',
dest='username',
default='admin',
help='The BMC username; defaults to "admin"')
parser_add.add_argument('--password',
parser.add_argument('--password',
dest='password',
default='password',
help='The BMC password; defaults to "password"')
parser_add.add_argument('--port',
parser.add_argument('--port',
dest='port',
type=int,
default=623,
help='Port to listen on; defaults to 623')
parser_add.add_argument('--address',
parser.add_argument('--address',
dest='address',
default='::',
help=('The address to bind to (IPv4 and IPv6 '
'are supported); defaults to ::'))
parser_add.add_argument('--libvirt-uri',
parser.add_argument('--libvirt-uri',
dest='libvirt_uri',
default="qemu:///system",
help=('The libvirt URI; defaults to '
'"qemu:///system"'))
parser_add.add_argument('--libvirt-sasl-username',
parser.add_argument('--libvirt-sasl-username',
dest='libvirt_sasl_username',
default=None,
help=('The libvirt SASL username; defaults to '
'None'))
parser_add.add_argument('--libvirt-sasl-password',
parser.add_argument('--libvirt-sasl-password',
dest='libvirt_sasl_password',
default=None,
help=('The libvirt SASL password; defaults to '
'None'))
return parser
# create the parser for the "delete" command
parser_delete = subparsers.add_parser('delete',
help='Delete a virtual BMC')
parser_delete.add_argument('domain_names', nargs='+',
help='A list of virtual machine names')
def take_action(self, args):
# create the parser for the "start" command
parser_start = subparsers.add_parser('start', help='Start a virtual BMC')
parser_start.add_argument('domain_name',
help='The name of the virtual machine')
log = logging.getLogger(__name__)
# create the parser for the "stop" command
parser_stop = subparsers.add_parser('stop', help='Stop a virtual BMC')
parser_stop.add_argument('domain_names', nargs='+',
help='A list of virtual machine names')
# Check if the username and password were given for SASL
sasl_user = args.libvirt_sasl_username
sasl_pass = args.libvirt_sasl_password
if any((sasl_user, sasl_pass)):
if not all((sasl_user, sasl_pass)):
msg = ("A password and username are required to use "
"Libvirt's SASL authentication")
log.error(msg)
raise exception.VirtualBMCError(msg)
# create the parser for the "list" command
subparsers.add_parser('list', help='list all virtual BMCs')
self.app.manager.add(username=args.username, password=args.password,
port=args.port, address=args.address,
domain_name=args.domain_name,
libvirt_uri=args.libvirt_uri,
libvirt_sasl_username=sasl_user,
libvirt_sasl_password=sasl_pass)
# create the parser for the "show" command
parser_show = subparsers.add_parser('show', help='Show a virtual BMC')
parser_show.add_argument('domain_name',
help='The name of the virtual machine')
args = parser.parse_args()
class DeleteCommand(Command):
"""Delete a virtual BMC for a virtual machine instance"""
manager = VirtualBMCManager()
def get_parser(self, prog_name):
parser = super(DeleteCommand, self).get_parser(prog_name)
try:
if args.command == 'add':
parser.add_argument('domain_names', nargs='+',
help='A list of virtual machine names')
# Check if the username and password were given for SASL
sasl_user = args.libvirt_sasl_username
sasl_pass = args.libvirt_sasl_password
if any((sasl_user, sasl_pass)):
if not all((sasl_user, sasl_pass)):
print("A password and username are required to use "
"Libvirt's SASL authentication", file=sys.stderr)
sys.exit(1)
return parser
manager.add(username=args.username, password=args.password,
port=args.port, address=args.address,
domain_name=args.domain_name,
libvirt_uri=args.libvirt_uri,
libvirt_sasl_username=sasl_user,
libvirt_sasl_password=sasl_pass)
def take_action(self, args):
for domain in args.domain_names:
self.app.manager.delete(domain)
elif args.command == 'delete':
for domain in args.domain_names:
manager.delete(domain)
elif args.command == 'start':
manager.start(args.domain_name)
class StartCommand(Command):
"""Start a virtual BMC for a virtual machine instance"""
elif args.command == 'stop':
for domain in args.domain_names:
manager.stop(domain)
def get_parser(self, prog_name):
parser = super(StartCommand, self).get_parser(prog_name)
elif args.command == 'list':
fields = ('Domain name', 'Status', 'Address', 'Port')
ptable = PrettyTable(fields)
for bmc in manager.list():
ptable.add_row([bmc['domain_name'], bmc['status'],
bmc['address'], bmc['port']])
print(ptable.get_string(sortby=fields[0]))
parser.add_argument('domain_name',
help='The name of the virtual machine')
elif args.command == 'show':
ptable = PrettyTable(['Property', 'Value'])
bmc = manager.show(args.domain_name)
for key, val in sorted(bmc.items()):
ptable.add_row([key, val])
print(ptable.get_string())
return parser
except exception.VirtualBMCError as e:
print(e, file=sys.stderr)
sys.exit(1)
def take_action(self, args):
self.app.manager.start(args.domain_name)
class StopCommand(Command):
"""Stop a virtual BMC for a virtual machine instance"""
def get_parser(self, prog_name):
parser = super(StopCommand, self).get_parser(prog_name)
parser.add_argument('domain_names', nargs='+',
help='A list of virtual machine names')
return parser
def take_action(self, args):
for domain_name in args.domain_names:
self.app.manager.stop(domain_name)
class ListCommand(Lister):
"""List all virtual BMC instances"""
def take_action(self, args):
header = ('Domain name', 'Status', 'Address', 'Port')
rows = []
for bmc in self.app.manager.list():
rows.append(
([bmc['domain_name'], bmc['status'],
bmc['address'], bmc['port']])
)
return header, sorted(rows)
class ShowCommand(Lister):
"""Show virtual BMC properties"""
def get_parser(self, prog_name):
parser = super(ShowCommand, self).get_parser(prog_name)
parser.add_argument('domain_name',
help='The name of the virtual machine')
return parser
def take_action(self, args):
header = ('Property', 'Value')
rows = []
bmc = self.app.manager.show(args.domain_name)
for key, val in bmc.items():
rows.append((key, val))
return header, sorted(rows)
class VirtualBMCApp(App):
def __init__(self):
super(VirtualBMCApp, self).__init__(
description='Virtual Baseboard Management Controller (BMC) backed '
'by virtual machines',
version=virtualbmc.__version__,
command_manager=CommandManager('virtualbmc'),
deferred_help=True,
)
def initialize_app(self, argv):
self.manager = VirtualBMCManager()
def clean_up(self, cmd, result, err):
self.LOG.debug('clean_up %s', cmd.__class__.__name__)
if err:
self.LOG.debug('got an error: %s', err)
def main(argv=sys.argv[1:]):
vbmc_app = VirtualBMCApp()
return vbmc_app.run(argv)
if __name__ == '__main__':
main()
sys.exit(main())

View File

@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import six
import sys
@ -26,7 +25,6 @@ from virtualbmc.tests.unit import utils as test_utils
@mock.patch.object(sys, 'exit', lambda _: None)
@mock.patch.object(argparse, 'ArgumentParser')
class VBMCTestCase(base.TestCase):
def setUp(self):
@ -34,54 +32,39 @@ class VBMCTestCase(base.TestCase):
self.domain = test_utils.get_domain()
@mock.patch.object(manager.VirtualBMCManager, 'add')
def test_main_add(self, mock_add, mock_parser):
args = mock.Mock()
args.parse_args.return_value = mock.Mock(command='add', **self.domain)
mock_parser.return_value = args
vbmc.main()
args.parse_args.assert_called_once_with()
def test_main_add(self, mock_add):
argv = ['add']
for option, value in self.domain.items():
if option != 'domain_name':
argv.append('--' + option.replace('_', '-'))
argv.append(value and str(value))
argv.append(self.domain['domain_name'])
vbmc.main(argv)
mock_add.assert_called_once_with(**self.domain)
@mock.patch.object(manager.VirtualBMCManager, 'delete')
def test_main_delete(self, mock_delete, mock_parser):
args = mock.Mock()
args.parse_args.return_value = mock.Mock(command='delete',
domain_names=['foo', 'bar'])
mock_parser.return_value = args
vbmc.main()
args.parse_args.assert_called_once_with()
def test_main_delete(self, mock_delete):
argv = ['delete', 'foo', 'bar']
vbmc.main(argv)
expected_calls = [mock.call('foo'), mock.call('bar')]
self.assertEqual(expected_calls, mock_delete.call_args_list)
@mock.patch.object(manager.VirtualBMCManager, 'start')
def test_main_start(self, mock_start, mock_parser):
args = mock.Mock()
args.parse_args.return_value = mock.Mock(command='start',
domain_name='SpongeBob')
mock_parser.return_value = args
vbmc.main()
args.parse_args.assert_called_once_with()
def test_main_start(self, mock_start):
argv = ['start', 'SpongeBob']
vbmc.main(argv)
mock_start.assert_called_once_with('SpongeBob')
@mock.patch.object(manager.VirtualBMCManager, 'stop')
def test_main_stop(self, mock_stop, mock_parser):
args = mock.Mock()
args.parse_args.return_value = mock.Mock(command='stop',
domain_names=['foo', 'bar'])
mock_parser.return_value = args
vbmc.main()
args.parse_args.assert_called_once_with()
def test_main_stop(self, mock_stop):
argv = ['stop', 'foo', 'bar']
vbmc.main(argv)
expected_calls = [mock.call('foo'), mock.call('bar')]
self.assertEqual(expected_calls, mock_stop.call_args_list)
@mock.patch.object(manager.VirtualBMCManager, 'list')
def test_main_list(self, mock_list, mock_parser):
args = mock.Mock()
args.parse_args.return_value = mock.Mock(command='list')
mock_parser.return_value = args
def test_main_list(self, mock_list):
argv = ['list']
mock_list.return_value = [
{'domain_name': 'node-1',
@ -94,49 +77,45 @@ class VBMCTestCase(base.TestCase):
'port': 123}]
with mock.patch.object(sys, 'stdout', six.StringIO()) as output:
vbmc.main()
vbmc.main(argv)
out = output.getvalue()
expected_output = """\
+-------------+---------+---------+------+
| Domain name | Status | Address | Port |
| Domain name | Status | Address | Port |
+-------------+---------+---------+------+
| node-0 | running | :: | 123 |
| node-1 | running | :: | 321 |
| node-0 | running | :: | 123 |
| node-1 | running | :: | 321 |
+-------------+---------+---------+------+
"""
self.assertEqual(expected_output, out)
args.parse_args.assert_called_once_with()
mock_list.assert_called_once_with()
self.assertEqual(mock_list.call_count, 1)
@mock.patch.object(manager.VirtualBMCManager, 'show')
def test_main_show(self, mock_show, mock_parser):
args = mock.Mock()
args.parse_args.return_value = mock.Mock(command='show',
domain_name='SpongeBob')
mock_parser.return_value = args
def test_main_show(self, mock_show):
argv = ['show', 'SpongeBob']
self.domain['status'] = 'running'
mock_show.return_value = self.domain
with mock.patch.object(sys, 'stdout', six.StringIO()) as output:
vbmc.main()
vbmc.main(argv)
out = output.getvalue()
expected_output = """\
+-----------------------+-----------+
| Property | Value |
| Property | Value |
+-----------------------+-----------+
| address | :: |
| domain_name | SpongeBob |
| libvirt_sasl_password | None |
| libvirt_sasl_username | None |
| libvirt_uri | foo://bar |
| password | pass |
| port | 123 |
| status | running |
| username | admin |
| address | :: |
| domain_name | SpongeBob |
| libvirt_sasl_password | None |
| libvirt_sasl_username | None |
| libvirt_uri | foo://bar |
| password | pass |
| port | 123 |
| status | running |
| username | admin |
+-----------------------+-----------+
"""
self.assertEqual(expected_output, out)
args.parse_args.assert_called_once_with()
mock_show.assert_called_once_with('SpongeBob')
self.assertEqual(mock_show.call_count, 1)