Merge "Configuration engine for image generation CLI"

This commit is contained in:
Jenkins 2016-07-28 12:15:41 +00:00 committed by Gerrit Code Review
commit 9e25355d94
5 changed files with 116 additions and 33 deletions

View File

@ -104,10 +104,22 @@ class ImageRemote(remote.TerminalOnlyRemote):
return self.guest.inspect_get_distro()
def pack_image(plugin_name, plugin_version, image_path, root_drive=None,
test_only=False, **kwargs):
def setup_plugins():
plugins_base.setup_plugins()
def get_plugin_arguments(plugin_name):
"""Gets plugin arguments, as a dict of version to argument list."""
plugin = plugins_base.PLUGINS.get_plugin(plugin_name)
versions = plugin.get_versions()
return {version: plugin.get_image_arguments(version)
for version in versions}
def pack_image(image_path, plugin_name, plugin_version, image_arguments,
root_drive=None, test_only=False):
with ImageRemote(image_path, root_drive) as image_remote:
plugin = plugins_base.PLUGINS.get_plugin(plugin_name)
reconcile = not test_only
plugin.pack_image(image_remote, reconcile=reconcile, env_map=kwargs)
plugin = plugins_base.PLUGINS.get_plugin(plugin_name)
plugin.pack_image(plugin_version, image_remote, reconcile=reconcile,
image_arguments=image_arguments)

View File

@ -16,50 +16,36 @@ import sys
from oslo_config import cfg
from oslo_log import log
import six
from sahara.cli.image_pack import api
from sahara.i18n import _
from sahara.i18n import _LI
LOG = log.getLogger(__name__)
CONF = cfg.CONF
CONF.register_cli_opts([
cfg.StrOpt(
'plugin',
required=True,
help="The name of the Sahara plugin for which you would like to "
"generate an image. Use sahara-image-create -p PLUGIN -h to "
"see a set of versions for a specific plugin."),
cfg.StrOpt(
'plugin-version',
dest='plugin_version',
required=True,
help="The version of the Sahara plugin for which you would like to "
"generate an image. Use sahara-image-create -p PLUGIN -v "
"VERSION -h to see a full set of arguments for a specific plugin "
"and version."),
cfg.StrOpt(
'image',
required=True,
help="The path to an image to modify. This image will be modified "
"in-place: be sure to target a copy if you wish to maintain a "
"clean master image."),
help=_("The path to an image to modify. This image will be modified "
"in-place: be sure to target a copy if you wish to maintain a "
"clean master image.")),
cfg.StrOpt(
'root-filesystem',
dest='root_fs',
required=False,
help="The filesystem to mount as the root volume on the image. No"
"value is required if only one filesystem is detected."),
help=_("The filesystem to mount as the root volume on the image. No"
"value is required if only one filesystem is detected.")),
cfg.BoolOpt(
'test-only',
dest='test_only',
default=False,
help="If this flag is set, no changes will be made to the image; "
"instead, the script will fail if discrepancies are found "
"between the image and the intended state."),
])
help=_("If this flag is set, no changes will be made to the image; "
"instead, the script will fail if discrepancies are found "
"between the image and the intended state."))])
def unregister_extra_cli_opt(name):
@ -75,6 +61,47 @@ for extra_opt in ["log-exchange", "host", "port"]:
unregister_extra_cli_opt(extra_opt)
def add_plugin_parsers(subparsers):
api.setup_plugins()
for plugin in CONF.plugins:
args_by_version = api.get_plugin_arguments(plugin)
if all(args is NotImplemented for version, args
in six.iteritems(args_by_version)):
continue
plugin_parser = subparsers.add_parser(
plugin, help=_('Image generation for the {plugin} plugin').format(
plugin=plugin))
version_parsers = plugin_parser.add_subparsers(
title=_("Plugin version"),
dest="version",
help=_("Available versions"))
for version, args in six.iteritems(args_by_version):
if not args:
continue
version_parser = version_parsers.add_parser(
version, help=_('{plugin} version {version}').format(
plugin=plugin, version=version))
for arg in args:
arg_token = ("--%s" % arg.name if len(arg.name) > 1 else
"-%s" % arg.name)
version_parser.add_argument(arg_token,
dest=arg.target_variable,
help=arg.description,
default=arg.default,
required=arg.required,
choices=arg.choices)
version_parser.set_defaults(args={arg.target_variable
for arg in args})
command_opt = cfg.SubCommandOpt('plugin',
title=_('Plugin'),
help=_('Available plugins'),
handler=add_plugin_parsers)
CONF.register_cli_opt(command_opt)
def main():
CONF(project='sahara')
@ -86,8 +113,13 @@ def main():
api.set_logger(LOG)
api.set_conf(CONF)
api.pack_image(CONF.plugin, CONF.plugin_version, CONF.image,
plugin = CONF.plugin.name
version = CONF.plugin.version
args = CONF.plugin.args
image_arguments = {arg: getattr(CONF.plugin, arg) for arg in args}
api.pack_image(CONF.image, plugin, version, image_arguments,
CONF.root_fs, CONF.test_only)
LOG.info(_LI("Finished packing image for {plugin} at version {version}"
).format(plugin=CONF.plugin, version=CONF.plugin_version))
LOG.info(_LI("Finished packing image for {plugin} at version {version}")
.format(plugin=plugin, version=version))

View File

@ -70,6 +70,19 @@ def validate_instance(instance, validators, reconcile=True, **kwargs):
validator.validate(remote, reconcile=reconcile, **kwargs)
class ImageArgument(object):
"""An argument used by an image manifest."""
def __init__(self, name, target_variable, description=None,
default=None, required=False, choices=None):
self.name = name
self.target_variable = target_variable
self.description = description
self.default = default
self.required = required
self.choices = choices
@six.add_metaclass(abc.ABCMeta)
class ImageValidator(object):
"""Validates the image spawned to an instance via a set of rules."""

View File

@ -93,6 +93,16 @@ class ProvisioningPluginBase(plugins_base.PluginInterface):
def decommission_nodes(self, cluster, instances):
pass
@plugins_base.optional
def get_image_arguments(self, hadoop_version):
"""Gets the argument set taken by the plugin's image generator"""
return NotImplemented
@plugins_base.optional
def pack_image(self, hadoop_version, remote,
reconcile=True, image_arguments=None):
pass
@plugins_base.optional
def validate_images(self, cluster, reconcile=True):
pass

View File

@ -43,8 +43,8 @@ class TestSaharaImagePackAPI(base.SaharaTestCase):
get_plugin=mock.Mock(return_value=plugin))
api.pack_image(
"plugin_name", "plugin_version", "image_path",
root_drive=None, test_only=False)
"image_path", "plugin_name", "plugin_version",
{"anarg": "avalue"}, root_drive=None, test_only=False)
guest.add_drive_opts.assert_called_with("image_path", format="qcow2")
guest.set_network.assert_called_with(True)
@ -54,3 +54,19 @@ class TestSaharaImagePackAPI(base.SaharaTestCase):
guest.sync.assert_called_once_with()
guest.umount_all.assert_called_once_with()
guest.close.assert_called_once_with()
@mock.patch('sahara.cli.image_pack.api.plugins_base')
def test_get_plugin_arguments(self, mock_plugins_base):
api.setup_plugins()
mock_plugins_base.setup_plugins.assert_called_once_with()
mock_PLUGINS = mock.Mock()
mock_plugins_base.PLUGINS = mock_PLUGINS
mock_plugin = mock.Mock()
mock_plugin.get_versions = mock.Mock(return_value=['1'])
mock_plugin.get_image_arguments = mock.Mock(
return_value=["Argument!"])
mock_PLUGINS.get_plugin = mock.Mock(return_value=mock_plugin)
result = api.get_plugin_arguments('Plugin!')
mock_plugin.get_versions.assert_called_once_with()
mock_plugin.get_image_arguments.assert_called_once_with('1')
self.assertEqual(result, {'1': ['Argument!']})